Compare commits
No commits in common. "main" and "dev-sabda" have entirely different histories.
45
.drone.yml
45
.drone.yml
|
|
@ -1,45 +0,0 @@
|
||||||
kind: pipeline
|
|
||||||
type: ssh
|
|
||||||
name: mediahub-fe-build-deploy
|
|
||||||
|
|
||||||
server:
|
|
||||||
host:
|
|
||||||
from_secret: ssh_host
|
|
||||||
user:
|
|
||||||
from_secret: ssh_user
|
|
||||||
ssh_key:
|
|
||||||
from_secret: ssh_key
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: prepare repo
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- dev-sabda-v2
|
|
||||||
- main
|
|
||||||
- prod
|
|
||||||
commands:
|
|
||||||
- rm -rf /opt/build/mediahub-fe
|
|
||||||
- mkdir -p /opt/build
|
|
||||||
- cd /opt/build
|
|
||||||
- git clone http://38.47.180.165:3000/mediahub/mediahub-fe.git
|
|
||||||
|
|
||||||
- name: build image
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- dev-sabda-v2
|
|
||||||
- prod
|
|
||||||
commands:
|
|
||||||
- docker login 38.47.180.165:3000 -u administrator -p HarborDockerImageRep0
|
|
||||||
- cd /opt/build/mediahub-fe
|
|
||||||
- docker build -t 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH .
|
|
||||||
- docker push 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
|
||||||
|
|
||||||
- name: deploy
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- prod
|
|
||||||
commands:
|
|
||||||
- docker pull 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
|
||||||
- docker stop new-mediahub-fe || true
|
|
||||||
- docker rm new-mediahub-fe || true
|
|
||||||
- docker run -dt -p 4200:3000 --restart always --name new-mediahub-fe 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
|
||||||
5
.env
5
.env
|
|
@ -1,3 +1,2 @@
|
||||||
NEXT_PUBLIC_API=https://new.netidhub.com/api
|
NEXT_PUBLIC_API=https://netidhub.com/api
|
||||||
NEXT_PUBLIC=https://new.netidhub.com
|
NEXT_PUBLIC=https://netidhub.com
|
||||||
NEXT_PUBLIC_TINYMCE_API_KEY=bhteuja26yz5p0aubxry9b95hs33amgn65kjv5km0fd5iuev
|
|
||||||
|
|
@ -7,24 +7,23 @@ build-dev:
|
||||||
when: on_success
|
when: on_success
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- dev-landing-v2
|
image: docker:stable
|
||||||
image:
|
|
||||||
name: docker:25.0.3-cli
|
|
||||||
services:
|
services:
|
||||||
- name: docker:25.0.3-dind
|
- name: docker:dind
|
||||||
command: ["--insecure-registry=38.47.185.86:8900"]
|
command: ["--insecure-registry=103.82.242.92:8900"]
|
||||||
script:
|
script:
|
||||||
- docker logout
|
- docker logout
|
||||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900
|
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
||||||
- docker build -t 38.47.185.86:8900/mediahub/new-mediahub-fe:dev .
|
- docker build -t 103.82.242.92:8900/mediahub/new-mediahub-fe:dev .
|
||||||
- docker push 38.47.185.86:8900/mediahub/new-mediahub-fe:dev
|
- docker push 103.82.242.92:8900/mediahub/new-mediahub-fe:dev
|
||||||
|
|
||||||
auto-deploy:
|
auto-deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
when: on_success
|
when: on_success
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- dev-landing-v2
|
|
||||||
image: curlimages/curl:latest
|
image: curlimages/curl:latest
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
script:
|
script:
|
||||||
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub
|
- curl --user admin:$JENKINS_PWD http://38.47.180.165:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub
|
||||||
|
|
|
||||||
44
Dockerfile
44
Dockerfile
|
|
@ -10,9 +10,8 @@ RUN npm install -g pnpm
|
||||||
# Membuat direktori aplikasi dan mengatur sebagai working directory
|
# Membuat direktori aplikasi dan mengatur sebagai working directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|
||||||
# Menyalin file penting terlebih dahulu untuk caching
|
# Menyalin file penting terlebih dahulu untuk caching
|
||||||
COPY package.json ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
# Menyalin direktori ckeditor5 jika diperlukan
|
# Menyalin direktori ckeditor5 jika diperlukan
|
||||||
COPY vendor/ckeditor5 ./vendor/ckeditor5
|
COPY vendor/ckeditor5 ./vendor/ckeditor5
|
||||||
|
|
@ -25,49 +24,10 @@ RUN pnpm install
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build aplikasi
|
# Build aplikasi
|
||||||
RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm run build
|
RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm next build
|
||||||
|
|
||||||
# Expose port untuk server
|
# Expose port untuk server
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Perintah untuk menjalankan aplikasi
|
# Perintah untuk menjalankan aplikasi
|
||||||
CMD ["pnpm", "run", "start"]
|
CMD ["pnpm", "run", "start"]
|
||||||
|
|
||||||
|
|
||||||
# # Gunakan base image Node.js Alpine yang ringan
|
|
||||||
# FROM node:23.5.0-alpine
|
|
||||||
|
|
||||||
# # Atur environment
|
|
||||||
# ENV PORT=3000
|
|
||||||
# ENV NODE_ENV=production
|
|
||||||
# ENV NODE_OPTIONS="--max-old-space-size=4096"
|
|
||||||
|
|
||||||
# # Install dependencies global
|
|
||||||
# RUN npm install -g pnpm pm2
|
|
||||||
|
|
||||||
# # Set working directory
|
|
||||||
# WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# # Salin file penting untuk caching dependencies
|
|
||||||
# COPY package.json pnpm-lock.yaml* ./
|
|
||||||
|
|
||||||
# # Salin vendor jika diperlukan (ckeditor misalnya)
|
|
||||||
# COPY vendor/ckeditor5 ./vendor/ckeditor5
|
|
||||||
|
|
||||||
# # Install dependencies
|
|
||||||
# RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
# # Salin semua source code
|
|
||||||
# COPY . .
|
|
||||||
|
|
||||||
# # Salin ecosystem config
|
|
||||||
# COPY ecosystem.config.js ./
|
|
||||||
|
|
||||||
# # Build Next.js
|
|
||||||
# RUN pnpm run build
|
|
||||||
|
|
||||||
# # Expose port
|
|
||||||
# EXPOSE 3000
|
|
||||||
|
|
||||||
# # Jalankan Next.js dalam mode cluster
|
|
||||||
# CMD ["pm2-runtime", "start", "ecosystem.config.js"]
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
||||||
import { AuthPage } from '../app/[locale]/auth/page';
|
|
||||||
import { useEmailValidation } from '../hooks/use-auth';
|
|
||||||
|
|
||||||
// Mock the hooks
|
|
||||||
jest.mock('../hooks/use-auth', () => ({
|
|
||||||
useAuth: () => ({
|
|
||||||
login: jest.fn(),
|
|
||||||
logout: jest.fn(),
|
|
||||||
refreshToken: jest.fn(),
|
|
||||||
isAuthenticated: false,
|
|
||||||
user: null,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
}),
|
|
||||||
useEmailValidation: jest.fn(),
|
|
||||||
useEmailSetup: () => ({
|
|
||||||
setupEmail: jest.fn(),
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
}),
|
|
||||||
useOTPVerification: () => ({
|
|
||||||
verifyOTP: jest.fn(),
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock the components
|
|
||||||
jest.mock('../components/auth/auth-layout', () => ({
|
|
||||||
AuthLayout: ({ children }: { children: React.ReactNode }) => <div data-testid="auth-layout">{children}</div>,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../components/auth/login-form', () => ({
|
|
||||||
LoginForm: ({ onSuccess, onError }: any) => (
|
|
||||||
<div data-testid="login-form">
|
|
||||||
<button onClick={() => onSuccess({ username: 'testuser', password: 'testpass' })}>
|
|
||||||
Login Success
|
|
||||||
</button>
|
|
||||||
<button onClick={() => onError('Login failed')}>
|
|
||||||
Login Error
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../components/auth/email-setup-form', () => ({
|
|
||||||
EmailSetupForm: ({ onSuccess, onError, onBack }: any) => (
|
|
||||||
<div data-testid="email-setup-form">
|
|
||||||
<button onClick={() => onSuccess()}>Email Setup Success</button>
|
|
||||||
<button onClick={() => onError('Email setup failed')}>Email Setup Error</button>
|
|
||||||
<button onClick={() => onBack()}>Back</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../components/auth/otp-form', () => ({
|
|
||||||
OTPForm: ({ onSuccess, onError, onResend }: any) => (
|
|
||||||
<div data-testid="otp-form">
|
|
||||||
<button onClick={() => onSuccess()}>OTP Success</button>
|
|
||||||
<button onClick={() => onError('OTP failed')}>OTP Error</button>
|
|
||||||
<button onClick={() => onResend()}>Resend OTP</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock toast
|
|
||||||
jest.mock('sonner', () => ({
|
|
||||||
toast: {
|
|
||||||
error: jest.fn(),
|
|
||||||
success: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Auth Flow', () => {
|
|
||||||
const mockValidateEmail = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
(useEmailValidation as jest.Mock).mockReturnValue({
|
|
||||||
validateEmail: mockValidateEmail,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should start with login form', () => {
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should transition to email setup when validation returns "setup"', async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue('setup');
|
|
||||||
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
|
|
||||||
// Click login success to trigger email validation
|
|
||||||
fireEvent.click(screen.getByText('Login Success'));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('email-setup-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should transition to OTP when validation returns "otp"', async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue('otp');
|
|
||||||
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
|
|
||||||
// Click login success to trigger email validation
|
|
||||||
fireEvent.click(screen.getByText('Login Success'));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('otp-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stay on login when validation returns "success"', async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue('success');
|
|
||||||
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
|
|
||||||
// Click login success to trigger email validation
|
|
||||||
fireEvent.click(screen.getByText('Login Success'));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should transition from email setup to OTP', async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue('setup');
|
|
||||||
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
|
|
||||||
// First, go to email setup
|
|
||||||
fireEvent.click(screen.getByText('Login Success'));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('email-setup-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then go to OTP
|
|
||||||
fireEvent.click(screen.getByText('Email Setup Success'));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('otp-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should go back from email setup to login', async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue('setup');
|
|
||||||
|
|
||||||
render(<AuthPage params={{ locale: 'en' }} />);
|
|
||||||
|
|
||||||
// First, go to email setup
|
|
||||||
fireEvent.click(screen.getByText('Login Success'));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('email-setup-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then go back to login
|
|
||||||
fireEvent.click(screen.getByText('Back'));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
||||||
import { LoginForm } from "@/components/auth/login-form";
|
|
||||||
import { useAuth, useEmailValidation } from "@/hooks/use-auth";
|
|
||||||
|
|
||||||
// Mock the hooks
|
|
||||||
jest.mock("@/hooks/use-auth");
|
|
||||||
jest.mock("@/service/landing/landing", () => ({
|
|
||||||
listRole: jest.fn().mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
data: [
|
|
||||||
{ id: 1, name: "Admin" },
|
|
||||||
{ id: 2, name: "User" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock next-intl
|
|
||||||
jest.mock("next-intl", () => ({
|
|
||||||
useTranslations: () => (key: string, options?: any) => {
|
|
||||||
const defaults = {
|
|
||||||
logInPlease: "Log In Please",
|
|
||||||
acc: "Acc",
|
|
||||||
register: "Register",
|
|
||||||
password: "Password",
|
|
||||||
rememberMe: "Remember Me",
|
|
||||||
forgotPass: "Forgot Pass",
|
|
||||||
next: "Next",
|
|
||||||
categoryReg: "Category Reg",
|
|
||||||
selectOne: "Select One",
|
|
||||||
signIn: "Sign In",
|
|
||||||
};
|
|
||||||
return options?.defaultValue || defaults[key] || key;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
|
|
||||||
const mockUseEmailValidation = useEmailValidation as jest.MockedFunction<typeof useEmailValidation>;
|
|
||||||
|
|
||||||
describe("LoginForm", () => {
|
|
||||||
const mockLogin = jest.fn();
|
|
||||||
const mockValidateEmail = jest.fn();
|
|
||||||
const mockOnSuccess = jest.fn();
|
|
||||||
const mockOnError = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
mockUseAuth.mockReturnValue({
|
|
||||||
login: mockLogin,
|
|
||||||
logout: jest.fn(),
|
|
||||||
refreshToken: jest.fn(),
|
|
||||||
isAuthenticated: false,
|
|
||||||
user: null,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockUseEmailValidation.mockReturnValue({
|
|
||||||
validateEmail: mockValidateEmail,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders login form with all required fields", () => {
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText("Log In Please")).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole("button", { name: /selanjutnya/i })).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows validation errors for invalid input", async () => {
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole("button", { name: /selanjutnya/i });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByText("Username is required")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("Password is required")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles successful form submission", async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue("success");
|
|
||||||
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const usernameInput = screen.getByLabelText(/username/i);
|
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
|
||||||
const submitButton = screen.getByRole("button", { name: /selanjutnya/i });
|
|
||||||
|
|
||||||
fireEvent.change(usernameInput, { target: { value: "testuser" } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: "password123" } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockValidateEmail).toHaveBeenCalledWith({
|
|
||||||
username: "testuser",
|
|
||||||
password: "password123",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles email validation step", async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue("setup");
|
|
||||||
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const usernameInput = screen.getByLabelText(/username/i);
|
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
|
||||||
const submitButton = screen.getByRole("button", { name: /selanjutnya/i });
|
|
||||||
|
|
||||||
fireEvent.change(usernameInput, { target: { value: "testuser" } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: "password123" } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnError).toHaveBeenCalledWith("Email setup required");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles OTP step", async () => {
|
|
||||||
mockValidateEmail.mockResolvedValue("otp");
|
|
||||||
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const usernameInput = screen.getByLabelText(/username/i);
|
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
|
||||||
const submitButton = screen.getByRole("button", { name: /selanjutnya/i });
|
|
||||||
|
|
||||||
fireEvent.change(usernameInput, { target: { value: "testuser" } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: "password123" } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnError).toHaveBeenCalledWith("OTP verification required");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("toggles password visibility", () => {
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
|
||||||
const toggleButton = screen.getByLabelText(/show password/i);
|
|
||||||
|
|
||||||
expect(passwordInput).toHaveAttribute("type", "password");
|
|
||||||
|
|
||||||
fireEvent.click(toggleButton);
|
|
||||||
expect(passwordInput).toHaveAttribute("type", "text");
|
|
||||||
expect(screen.getByLabelText(/hide password/i)).toBeInTheDocument();
|
|
||||||
|
|
||||||
fireEvent.click(toggleButton);
|
|
||||||
expect(passwordInput).toHaveAttribute("type", "password");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles remember me checkbox", () => {
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const rememberMeCheckbox = screen.getByRole("checkbox", { name: /remember me/i });
|
|
||||||
|
|
||||||
expect(rememberMeCheckbox).toBeChecked();
|
|
||||||
|
|
||||||
fireEvent.click(rememberMeCheckbox);
|
|
||||||
expect(rememberMeCheckbox).not.toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("opens registration dialog", () => {
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const registerLink = screen.getByText("Register");
|
|
||||||
fireEvent.click(registerLink);
|
|
||||||
|
|
||||||
expect(screen.getByText("Category Reg")).toBeInTheDocument();
|
|
||||||
expect(screen.getByText("Select One")).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText("Admin")).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText("User")).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles loading state", async () => {
|
|
||||||
mockUseEmailValidation.mockReturnValue({
|
|
||||||
validateEmail: mockValidateEmail,
|
|
||||||
loading: true,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole("button", { name: /processing/i });
|
|
||||||
expect(submitButton).toBeDisabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles error state", async () => {
|
|
||||||
mockValidateEmail.mockRejectedValue(new Error("Validation failed"));
|
|
||||||
|
|
||||||
render(
|
|
||||||
<LoginForm
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onError={mockOnError}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const usernameInput = screen.getByLabelText(/username/i);
|
|
||||||
const passwordInput = screen.getByLabelText(/password/i);
|
|
||||||
const submitButton = screen.getByRole("button", { name: /selanjutnya/i });
|
|
||||||
|
|
||||||
fireEvent.change(usernameInput, { target: { value: "testuser" } });
|
|
||||||
fireEvent.change(passwordInput, { target: { value: "password123" } });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnError).toHaveBeenCalledWith("Validation failed");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
describe('Testing Setup', () => {
|
|
||||||
it('should work correctly', () => {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have testing library matchers', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.textContent = 'Hello World';
|
|
||||||
expect(element).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use server'
|
'use server'
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { postMessage } from "@/app/[locale]/(protected)/app/chat/utils";
|
||||||
|
|
||||||
|
|
||||||
export const postMessageAction = async (id: string, message: string,) => {
|
export const postMessageAction = async (id: string, message: string,) => {
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,29 @@ import {
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Link } from "@/components/navigation";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import {
|
||||||
import withReactContent from "sweetalert2-react-content";
|
formatDateToIndonesian,
|
||||||
import Swal from "sweetalert2";
|
getOnlyDate,
|
||||||
import { useRouter } from "next/navigation";
|
htmlToString,
|
||||||
import { deleteUser } from "@/service/management-user/management-user";
|
} from "@/utils/globals";
|
||||||
import { stringify } from "querystring";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
|
|
||||||
const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[] => [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "no",
|
accessorKey: "no",
|
||||||
header: "No",
|
header: "No",
|
||||||
|
|
@ -30,131 +44,49 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
|
||||||
header: "Nama",
|
header: "Nama",
|
||||||
cell: ({ row }) => <span>{row.getValue("fullname")}</span>,
|
cell: ({ row }) => <span>{row.getValue("fullname")}</span>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "address",
|
accessorKey: "address",
|
||||||
header: "Wilayah",
|
header: "Wilayah",
|
||||||
cell: () => <span>MABES</span>,
|
cell: ({ row }) => <span>{row.getValue("address")}</span>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "userRolePlacements",
|
|
||||||
header: "Posisi",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const placements = row.original.userRolePlacements || [];
|
|
||||||
const placement = placements.find(
|
|
||||||
(p: any) => p.roleId === 11 || p.roleId === 12
|
|
||||||
);
|
|
||||||
|
|
||||||
let posisi = "-";
|
|
||||||
if (placement) {
|
|
||||||
posisi = placement.roleId === 11 ? "Koorkurator" : "Kurator";
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span>{posisi}</span>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "role.name",
|
accessorKey: "role.name",
|
||||||
header: "Bidang Keahlian",
|
header: "Bidang Keahlian",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => <span>{row.original.role?.name ?? "-"}</span>,
|
||||||
<span>
|
|
||||||
{row.original.userProfilesAdditional?.userCompetency?.name ?? "-"}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "userExperienceId",
|
accessorKey: "experience",
|
||||||
header: "Pengalaman",
|
header: "Pengalaman",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => <span>{row.getValue("experience")}</span>,
|
||||||
const experienceId =
|
|
||||||
row.original.userProfilesAdditional?.userExperienceId;
|
|
||||||
|
|
||||||
const experienceMap: Record<number, string> = {
|
|
||||||
1: "Akademisi",
|
|
||||||
2: "Praktisi",
|
|
||||||
3: "Akademisi + Praktisi",
|
|
||||||
};
|
|
||||||
|
|
||||||
return <span>{experienceMap[experienceId] ?? "-"}</span>;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "experience",
|
||||||
|
header: "Posisi",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("experience")}</span>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { toast } = useToast();
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
const doDelete = async (id: number) => {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Menghapus user...",
|
|
||||||
text: "Mohon tunggu",
|
|
||||||
allowOutsideClick: false,
|
|
||||||
didOpen: () => Swal.showLoading(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await deleteUser(id);
|
|
||||||
|
|
||||||
Swal.close();
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
toast({
|
|
||||||
title: stringify(response?.message),
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({ title: "Berhasil menghapus user" });
|
|
||||||
|
|
||||||
// ⬅️ INI YANG PENTING → REFRESH TABLE TANPA RELOAD
|
|
||||||
onRefresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Hapus user ini?",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#dc3545",
|
|
||||||
confirmButtonText: "Iya",
|
|
||||||
cancelButtonText: "Tidak",
|
|
||||||
}).then((res) => {
|
|
||||||
if (res.isConfirmed) doDelete(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size="icon" variant="ghost">
|
<Button
|
||||||
<MoreVertical className="h-4 w-4" />
|
size="icon"
|
||||||
|
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link href={`/admin/add-experts/detail/${row.original.id}`}>
|
<Link href="#">Detail</Link>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" /> View
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href={`/admin/add-experts/update/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" /> Edit
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDelete(row.original.userKeycloakId)}
|
|
||||||
className="text-red-600 cursor-pointer hover:bg-red-300"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 me-1.5" /> Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
@ -162,4 +94,4 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default getColumns;
|
export default columns;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
SortingState,
|
SortingState,
|
||||||
|
|
@ -14,6 +15,7 @@ import {
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -23,6 +25,7 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { UserIcon } from "lucide-react";
|
import { UserIcon } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -32,14 +35,43 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { paginationBlog } from "@/service/blog/blog";
|
||||||
|
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
// import columns from "./column";
|
import columns from "./column";
|
||||||
import getColumns from "./column";
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { listDataMedia } from "@/service/broadcast/broadcast";
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
import { listDataExperts } from "@/service/experts/experts";
|
import { listDataExperts } from "@/service/experts/experts";
|
||||||
|
|
||||||
|
const dummyData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Prof. Dr. Ravi",
|
||||||
|
region: "Nasional",
|
||||||
|
skills: "Komunikasi",
|
||||||
|
experience: "Akademisi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Prof. Dr. Novan",
|
||||||
|
region: "DKI Jakarta",
|
||||||
|
skills: "Hukum",
|
||||||
|
experience: "Akademisi + Praktisi",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const AddExpertTable = () => {
|
const AddExpertTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
@ -65,8 +97,7 @@ const AddExpertTable = () => {
|
||||||
const [limit, setLimit] = React.useState(10);
|
const [limit, setLimit] = React.useState(10);
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
// columns,
|
columns,
|
||||||
columns: getColumns({ onRefresh: fetchData }),
|
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
|
@ -170,7 +201,7 @@ const AddExpertTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
|
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
|
||||||
<Link href="/admin/add-experts/create">
|
<Link href="/admin/add-experts/create">
|
||||||
|
|
@ -252,11 +283,7 @@ const AddExpertTable = () => {
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
// colSpan={columns.length}
|
|
||||||
colSpan={table.getAllLeafColumns().length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
No results.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
|
||||||
|
|
@ -26,70 +26,40 @@ import {
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AdministrationLevelList,
|
AdministrationLevelList,
|
||||||
checkRolePlacementsAvailability,
|
|
||||||
getListCompetencies,
|
getListCompetencies,
|
||||||
getListExperiences,
|
getListExperiences,
|
||||||
saveUserInternal,
|
saveUserInternal,
|
||||||
saveUserRolePlacements,
|
saveUserRolePlacements,
|
||||||
} from "@/service/management-user/management-user";
|
} from "@/service/management-user/management-user";
|
||||||
import { error, loading } from "@/config/swal";
|
import { loading } from "@/config/swal";
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
|
||||||
// const FormSchema = z.object({
|
|
||||||
// name: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// username: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// password: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// phoneNumber: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// email: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// skills: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// experiences: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// company: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
name: z.string({ required_error: "Required" }),
|
name: z.string({
|
||||||
username: z
|
required_error: "Required",
|
||||||
.string({ required_error: "Required" })
|
}),
|
||||||
.refine((val) => !/\s/.test(val), {
|
username: z.string({
|
||||||
message: "Username tidak boleh mengandung spasi",
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
password: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
phoneNumber: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
email: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
skills: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
experiences: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
company: z.string({
|
||||||
|
required_error: "Required",
|
||||||
}),
|
}),
|
||||||
// .transform((val) => val.toLowerCase()),
|
|
||||||
|
|
||||||
password: z
|
|
||||||
.string({ required_error: "Required" })
|
|
||||||
.min(8, "Minimal 8 karakter")
|
|
||||||
.regex(/[A-Z]/, "Harus mengandung huruf besar (A-Z)")
|
|
||||||
.regex(/[0-9]/, "Harus mengandung angka (0-9)")
|
|
||||||
.regex(/[^A-Za-z0-9]/, "Harus mengandung karakter spesial (!@#$%^&*)"),
|
|
||||||
|
|
||||||
// confirmPassword: z.string({ required_error: "Required" }),
|
|
||||||
|
|
||||||
phoneNumber: z.string({ required_error: "Required" }),
|
|
||||||
email: z.string({ required_error: "Required" }),
|
|
||||||
skills: z.string({ required_error: "Required" }),
|
|
||||||
experiences: z.string({ required_error: "Required" }),
|
|
||||||
company: z.string({ required_error: "Required" }),
|
|
||||||
});
|
});
|
||||||
// .refine((data) => data.password === data.confirmPassword, {
|
|
||||||
// path: ["confirmPassword"],
|
|
||||||
// message: "Konfirmasi password tidak sama",
|
|
||||||
// });
|
|
||||||
|
|
||||||
export type Placements = {
|
export type Placements = {
|
||||||
index: number;
|
index: number;
|
||||||
|
|
@ -103,7 +73,6 @@ export default function AddExpertForm() {
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
});
|
});
|
||||||
const [passwordStrength, setPasswordStrength] = useState("");
|
|
||||||
const [incrementId, setIncrementId] = useState(1);
|
const [incrementId, setIncrementId] = useState(1);
|
||||||
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
||||||
{ index: 0, roleId: "", userLevelId: 0 },
|
{ index: 0, roleId: "", userLevelId: 0 },
|
||||||
|
|
@ -161,30 +130,9 @@ export default function AddExpertForm() {
|
||||||
userCompetencyId: data.skills,
|
userCompetencyId: data.skills,
|
||||||
userExperienceId: data.experiences,
|
userExperienceId: data.experiences,
|
||||||
companyName: data.company,
|
companyName: data.company,
|
||||||
isAdmin: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loading();
|
loading();
|
||||||
|
|
||||||
// check availability first
|
|
||||||
var placementArr: any[] = [];
|
|
||||||
placementRows.forEach((row: any) => {
|
|
||||||
placementArr.push({
|
|
||||||
roleId: Number(row.roleId),
|
|
||||||
userLevelId: Number(row.userLevelId),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataReqAvail = {
|
|
||||||
placements: placementArr,
|
|
||||||
};
|
|
||||||
const resAvail = await checkRolePlacementsAvailability(dataReqAvail);
|
|
||||||
if (resAvail?.error) {
|
|
||||||
close();
|
|
||||||
error(resAvail.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await saveUserInternal(dataReq);
|
const res = await saveUserInternal(dataReq);
|
||||||
const resData = res?.data?.data;
|
const resData = res?.data?.data;
|
||||||
const userProfileId = resData?.id;
|
const userProfileId = resData?.id;
|
||||||
|
|
@ -282,26 +230,11 @@ export default function AddExpertForm() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddRow = () => {
|
const handleAddRow = () => {
|
||||||
if (placementRows.length < 2) {
|
setPlacementRows((prevRows: any) => [
|
||||||
setPlacementRows((prevRows) => [
|
|
||||||
...prevRows,
|
...prevRows,
|
||||||
{ index: incrementId, roleId: "", userLevelId: 0 },
|
{ index: incrementId, roleId: "", userLevelId: 0 },
|
||||||
]);
|
]);
|
||||||
setIncrementId((prevId) => prevId + 1);
|
setIncrementId((prevId) => prevId + 1);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const computeStrength = (password: string) => {
|
|
||||||
let score = 0;
|
|
||||||
if (password.length >= 8) score++;
|
|
||||||
if (/[A-Z]/.test(password)) score++;
|
|
||||||
if (/[0-9]/.test(password)) score++;
|
|
||||||
if (/[^A-Za-z0-9]/.test(password)) score++;
|
|
||||||
|
|
||||||
if (score <= 1) return "weak";
|
|
||||||
if (score === 2 || score === 3) return "medium";
|
|
||||||
if (score === 4) return "strong";
|
|
||||||
return "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -311,7 +244,7 @@ export default function AddExpertForm() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
className="space-y-3 bg-white rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -331,55 +264,21 @@ export default function AddExpertForm() {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
|
||||||
name="username"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Username (huruf kecil, tanpa spasi)</FormLabel>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={field.value}
|
|
||||||
placeholder="masukkan username"
|
|
||||||
onChange={(e) => {
|
|
||||||
let value = e.target.value;
|
|
||||||
|
|
||||||
// Hapus spasi otomatis
|
|
||||||
value = value.replace(/\s+/g, "");
|
|
||||||
|
|
||||||
// Jadikan lowercase otomatis
|
|
||||||
value = value.toLowerCase();
|
|
||||||
|
|
||||||
field.onChange(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Info tambahan */}
|
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
|
||||||
Username otomatis menjadi huruf kecil tanpa spasi.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="username"
|
name="username"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Username</FormLabel>
|
<FormLabel>Username</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
|
||||||
value={field.value}
|
value={field.value}
|
||||||
placeholder="Masukkan"
|
placeholder="Masukkan Username"
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
|
|
@ -389,25 +288,13 @@ export default function AddExpertForm() {
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
placeholder="Masukkan No.Hp"
|
placeholder="Masukkan Nama Lengkap"
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="usernamefake"
|
|
||||||
autoComplete="username"
|
|
||||||
hidden
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="passwordfake"
|
|
||||||
autoComplete="new-password"
|
|
||||||
hidden
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
|
|
@ -417,7 +304,7 @@ export default function AddExpertForm() {
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
placeholder="Masukkan email"
|
placeholder="Masukkan Nama Lengkap"
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|
@ -425,69 +312,6 @@ export default function AddExpertForm() {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Password</FormLabel>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
value={field.value}
|
|
||||||
placeholder="Masukkan Password"
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(e.target.value);
|
|
||||||
setPasswordStrength(computeStrength(e.target.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-default-500"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<FormLabel className="text-gray-400 text-[12px]">
|
|
||||||
Password harus memiliki minimal 8 karakter, special karakter,
|
|
||||||
angka dan huruf kapital
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
{/* Strength meter */}
|
|
||||||
{field.value && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<div
|
|
||||||
className={`h-2 rounded transition-all ${
|
|
||||||
passwordStrength === "weak"
|
|
||||||
? "bg-red-500 w-1/4"
|
|
||||||
: passwordStrength === "medium"
|
|
||||||
? "bg-yellow-500 w-2/4"
|
|
||||||
: "bg-green-500 w-full"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p
|
|
||||||
className={`text-xs mt-1 ${
|
|
||||||
passwordStrength === "weak"
|
|
||||||
? "text-red-500"
|
|
||||||
: passwordStrength === "medium"
|
|
||||||
? "text-yellow-600"
|
|
||||||
: "text-green-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{passwordStrength === "weak" && "Weak Password"}
|
|
||||||
{passwordStrength === "medium" && "Medium Password"}
|
|
||||||
{passwordStrength === "strong" && "Strong Password"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="password"
|
name="password"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
|
|
@ -512,7 +336,7 @@ export default function AddExpertForm() {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="skills"
|
name="skills"
|
||||||
|
|
@ -600,7 +424,7 @@ export default function AddExpertForm() {
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
{/* <Select
|
<Select
|
||||||
onValueChange={(e) =>
|
onValueChange={(e) =>
|
||||||
handleSelectionChange(row.index, "userLevelId", e)
|
handleSelectionChange(row.index, "userLevelId", e)
|
||||||
}
|
}
|
||||||
|
|
@ -617,20 +441,6 @@ export default function AddExpertForm() {
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select> */}
|
|
||||||
<Select
|
|
||||||
onValueChange={(e) =>
|
|
||||||
handleSelectionChange(row.index, "userLevelId", e)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Pilih User Level" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="216">MABES POLRI</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
</Select>
|
||||||
{placementRows.length > 1 && (
|
{placementRows.length > 1 && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -644,14 +454,9 @@ export default function AddExpertForm() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{/* <Button
|
<Button type="button" size="md" onClick={() => handleAddRow()}>
|
||||||
type="button"
|
|
||||||
size="md"
|
|
||||||
onClick={handleAddRow}
|
|
||||||
disabled={placementRows.length >= 2} // optional: disable button if already 1 row added
|
|
||||||
>
|
|
||||||
Tambah
|
Tambah
|
||||||
</Button> */}
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
|
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import FormDetailExperts from "@/components/form/experts/experts-detail";
|
|
||||||
|
|
||||||
const ExpertsDetailPage = async () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<FormDetailExperts />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExpertsDetailPage;
|
|
||||||
|
|
@ -1,719 +0,0 @@
|
||||||
"use client";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
AdministrationLevelList,
|
|
||||||
getListCompetencies,
|
|
||||||
getListExperiences,
|
|
||||||
getUserById,
|
|
||||||
saveUserInternal,
|
|
||||||
saveUserRolePlacements,
|
|
||||||
} from "@/service/management-user/management-user";
|
|
||||||
import { loading } from "@/config/swal";
|
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
import { useParams } from "next/navigation";
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
|
||||||
name: z.string().optional(),
|
|
||||||
username: z.string().optional(),
|
|
||||||
password: z.string().optional(),
|
|
||||||
phoneNumber: z.string().optional(),
|
|
||||||
email: z.string().optional(),
|
|
||||||
skills: z.string().optional(),
|
|
||||||
experiences: z.string().optional(),
|
|
||||||
company: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// const FormSchema = z.object({
|
|
||||||
// name: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// username: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// password: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// phoneNumber: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// email: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// skills: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// experiences: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// company: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
|
|
||||||
export type Placements = {
|
|
||||||
index: number;
|
|
||||||
roleId?: string;
|
|
||||||
userLevelId?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Detail {
|
|
||||||
id: string;
|
|
||||||
fullname: string;
|
|
||||||
username: string;
|
|
||||||
phoneNumber: string;
|
|
||||||
email: string;
|
|
||||||
birthPlace: string;
|
|
||||||
birthDate: string;
|
|
||||||
education: string;
|
|
||||||
career: string;
|
|
||||||
expertise: string;
|
|
||||||
experience: string;
|
|
||||||
position: string;
|
|
||||||
region: string;
|
|
||||||
cvUrl: string;
|
|
||||||
photoUrl: string;
|
|
||||||
isActive: boolean;
|
|
||||||
userProfilesAdditional?: {
|
|
||||||
companyName: string;
|
|
||||||
userExperienceId: any;
|
|
||||||
userCompetency?: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
isActive: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
userRolePlacements?: {
|
|
||||||
roleId: number;
|
|
||||||
userLevelId: number;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function UpdateExpertForm() {
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
const router = useRouter();
|
|
||||||
const params = useParams();
|
|
||||||
const id = params?.id;
|
|
||||||
const [detail, setDetail] = useState<Detail | null>(null);
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
resolver: zodResolver(FormSchema),
|
|
||||||
});
|
|
||||||
const [incrementId, setIncrementId] = useState(1);
|
|
||||||
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
|
||||||
{ index: 0, roleId: "", userLevelId: 0 },
|
|
||||||
]);
|
|
||||||
const [userCompetencies, setUserCompetencies] = useState<any>();
|
|
||||||
const [userExperiences, setUserExperiences] = useState<any>();
|
|
||||||
const [userLevels, setUserLevels] = useState<any>();
|
|
||||||
const [passwordType, setPasswordType] = useState("password");
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getDataAdditional();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function initState() {
|
|
||||||
if (id) {
|
|
||||||
const response = await getUserById(String(id));
|
|
||||||
const details = response?.data?.data;
|
|
||||||
setDetail(details);
|
|
||||||
}
|
|
||||||
if (detail?.userProfilesAdditional?.companyName) {
|
|
||||||
form.setValue("company", detail.userProfilesAdditional.companyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detail?.userProfilesAdditional?.userCompetency?.id) {
|
|
||||||
form.setValue(
|
|
||||||
"skills",
|
|
||||||
String(detail.userProfilesAdditional.userCompetency.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detail?.userProfilesAdditional?.userExperienceId) {
|
|
||||||
form.setValue(
|
|
||||||
"experiences",
|
|
||||||
String(detail.userProfilesAdditional.userExperienceId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initState();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!detail) return;
|
|
||||||
|
|
||||||
// Isi semua form field
|
|
||||||
form.reset({
|
|
||||||
name: detail.fullname || "",
|
|
||||||
username: detail.username || "",
|
|
||||||
phoneNumber: detail.phoneNumber || "",
|
|
||||||
email: detail.email || "",
|
|
||||||
password: "",
|
|
||||||
skills: detail?.userProfilesAdditional?.userCompetency?.id
|
|
||||||
? String(detail.userProfilesAdditional.userCompetency.id)
|
|
||||||
: "",
|
|
||||||
experiences: detail?.userProfilesAdditional?.userExperienceId
|
|
||||||
? String(detail.userProfilesAdditional.userExperienceId)
|
|
||||||
: "",
|
|
||||||
company: detail?.userProfilesAdditional?.companyName || "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔥 Masukkan posisi existing
|
|
||||||
if (detail.userRolePlacements && detail.userRolePlacements.length > 0) {
|
|
||||||
const mapped = detail.userRolePlacements.map(
|
|
||||||
(item: any, idx: number) => ({
|
|
||||||
index: idx,
|
|
||||||
roleId: String(item.roleId),
|
|
||||||
userLevelId: Number(item.userLevelId),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setPlacementRows(mapped);
|
|
||||||
}
|
|
||||||
}, [detail]);
|
|
||||||
|
|
||||||
if (!detail) return <div>Loading...</div>;
|
|
||||||
|
|
||||||
const togglePasswordType = () => {
|
|
||||||
setPasswordType((prevType) =>
|
|
||||||
prevType === "password" ? "text" : "password"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const roleSelection = [
|
|
||||||
{
|
|
||||||
id: "11",
|
|
||||||
name: "Koor Kurator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "12",
|
|
||||||
name: "Kurator",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Simpan Data",
|
|
||||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonColor: "#d33",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Simpan",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
save(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async (data: z.infer<typeof FormSchema>) => {
|
|
||||||
console.log("data", data);
|
|
||||||
|
|
||||||
const dataReq = {
|
|
||||||
id: detail?.id,
|
|
||||||
firstName: data.name || detail.fullname,
|
|
||||||
username: data.username || detail.username,
|
|
||||||
email: data.email || detail.email,
|
|
||||||
password: data.password || undefined,
|
|
||||||
address: "",
|
|
||||||
roleId: "EXP-ID",
|
|
||||||
phoneNumber: data.phoneNumber || detail.phoneNumber,
|
|
||||||
userCompetencyId:
|
|
||||||
data.skills || detail.userProfilesAdditional?.userCompetency?.id,
|
|
||||||
userExperienceId:
|
|
||||||
data.experiences || detail.userProfilesAdditional?.userExperienceId,
|
|
||||||
companyName: data.company || detail.userProfilesAdditional?.companyName,
|
|
||||||
isAdmin: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// const dataReq = {
|
|
||||||
// id: detail?.id,
|
|
||||||
// firstName: data.name,
|
|
||||||
// username: data.username,
|
|
||||||
// email: data.email,
|
|
||||||
// password: data.password,
|
|
||||||
// address: "",
|
|
||||||
// roleId: "EXP-ID",
|
|
||||||
// phoneNumber: data.phoneNumber,
|
|
||||||
// userCompetencyId: data.skills,
|
|
||||||
// userExperienceId: data.experiences,
|
|
||||||
// companyName: data.company,
|
|
||||||
// };
|
|
||||||
|
|
||||||
loading();
|
|
||||||
const res = await saveUserInternal(dataReq);
|
|
||||||
const resData = res?.data?.data;
|
|
||||||
const userProfileId = resData?.id;
|
|
||||||
|
|
||||||
var placementArr: any[] = [];
|
|
||||||
placementRows.forEach((row: any) => {
|
|
||||||
placementArr.push({
|
|
||||||
roleId: Number(row.roleId),
|
|
||||||
userLevelId: Number(row.userLevelId),
|
|
||||||
userProfileId: userProfileId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataReq2 = {
|
|
||||||
userId: userProfileId,
|
|
||||||
placements: placementArr,
|
|
||||||
};
|
|
||||||
const res2 = await saveUserRolePlacements(dataReq2);
|
|
||||||
const resData2 = res2?.data?.data;
|
|
||||||
|
|
||||||
success("/admin/add-experts");
|
|
||||||
};
|
|
||||||
|
|
||||||
function success(redirect: string): void {
|
|
||||||
MySwal.fire({
|
|
||||||
title: '<p class="text-green-600 font-bold">Sukses</p>',
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: '<span class="text-white">OK</span>',
|
|
||||||
allowOutsideClick: false,
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
router.push(redirect);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDataAdditional() {
|
|
||||||
const resCompetencies = await getListCompetencies();
|
|
||||||
setUserCompetencies(resCompetencies?.data?.data);
|
|
||||||
|
|
||||||
const resExperiences = await getListExperiences();
|
|
||||||
setUserExperiences(resExperiences?.data?.data);
|
|
||||||
console.log("experience", resExperiences?.data?.data);
|
|
||||||
|
|
||||||
const resUserLevels = await AdministrationLevelList();
|
|
||||||
const data = resUserLevels?.data?.data;
|
|
||||||
var levelsArr: any[] = [];
|
|
||||||
data.forEach((levels: any) => {
|
|
||||||
levelsArr.push({
|
|
||||||
id: levels.id,
|
|
||||||
label: levels.name,
|
|
||||||
name: levels.name,
|
|
||||||
value: String(levels.id),
|
|
||||||
levelNumber: levels.levelNumber,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setUserLevels(levelsArr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function successSubmit() {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Sukses",
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
router.push("/admin/add-experts");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectionChange = (
|
|
||||||
index: number,
|
|
||||||
type: "roleId" | "userLevelId",
|
|
||||||
value: string
|
|
||||||
) => {
|
|
||||||
setPlacementRows((prevRows) =>
|
|
||||||
prevRows.map((row) =>
|
|
||||||
row.index === index ? { ...row, [type]: value } : row
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveRow = (index: number) => {
|
|
||||||
console.log(index);
|
|
||||||
console.log(placementRows);
|
|
||||||
const newPlacements = placementRows.filter((row) => row.index != index);
|
|
||||||
console.log(newPlacements);
|
|
||||||
setPlacementRows(newPlacements);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddRow = () => {
|
|
||||||
if (placementRows.length < 2) {
|
|
||||||
setPlacementRows((prevRows) => [
|
|
||||||
...prevRows,
|
|
||||||
{ index: incrementId, roleId: "", userLevelId: 0 },
|
|
||||||
]);
|
|
||||||
setIncrementId((prevId) => prevId + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
{detail !== undefined ? (
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-3 bg-white rounded-sm p-4"
|
|
||||||
>
|
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="name"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Nama Lengkap</FormLabel>
|
|
||||||
{/* <Input
|
|
||||||
defaultValue={detail?.fullname}
|
|
||||||
placeholder="Masukkan Nama Lengkap"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
defaultValue={detail?.fullname}
|
|
||||||
placeholder="Masukkan Nama Lengkap"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
disabled
|
|
||||||
control={form.control}
|
|
||||||
name="username"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Username</FormLabel>
|
|
||||||
{/* <Input
|
|
||||||
type="text"
|
|
||||||
defaultValue={detail?.username}
|
|
||||||
placeholder="Masukkan"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
type="text"
|
|
||||||
defaultValue={detail?.username}
|
|
||||||
placeholder="Masukkan"
|
|
||||||
/>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="phoneNumber"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>No. HP</FormLabel>
|
|
||||||
{/* <Input
|
|
||||||
type="number"
|
|
||||||
defaultValue={detail?.phoneNumber}
|
|
||||||
placeholder="Masukkan No.Hp"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
type="number"
|
|
||||||
defaultValue={detail?.phoneNumber}
|
|
||||||
placeholder="Masukkan"
|
|
||||||
/>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
{/* <Input
|
|
||||||
type="email"
|
|
||||||
defaultValue={detail?.email}
|
|
||||||
placeholder="Masukkan email"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
type="email"
|
|
||||||
defaultValue={detail?.email}
|
|
||||||
placeholder="Masukkan email"
|
|
||||||
/>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Password (Opsional)</FormLabel>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Kosongkan jika tidak ingin mengubah password"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
{/* <FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Password</FormLabel>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
value={field.value}
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Masukkan Password"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-default-500 hover:text-default-700"
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="skills"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Bidang Keahlian</FormLabel>
|
|
||||||
<Select onValueChange={field.onChange} value={field.value}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={
|
|
||||||
detail?.userProfilesAdditional?.userCompetency?.name
|
|
||||||
? detail.userProfilesAdditional.userCompetency
|
|
||||||
.name
|
|
||||||
: "Pilih Bidang Keahlian"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{userCompetencies?.map((item: any) => (
|
|
||||||
<SelectItem key={item.id} value={String(item.id)}>
|
|
||||||
{item.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="experiences"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Pengalaman</FormLabel>
|
|
||||||
<Select onValueChange={field.onChange} value={field.value}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={
|
|
||||||
detail &&
|
|
||||||
detail.userProfilesAdditional &&
|
|
||||||
detail.userProfilesAdditional.userExperienceId
|
|
||||||
? userExperiences?.find(
|
|
||||||
(item: any) =>
|
|
||||||
item.id ===
|
|
||||||
detail.userProfilesAdditional!
|
|
||||||
.userExperienceId
|
|
||||||
)?.name
|
|
||||||
: "Pilih Pengalaman"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{userExperiences?.map((item: any) => (
|
|
||||||
<SelectItem key={item.id} value={String(item.id)}>
|
|
||||||
{item.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="company"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Nama Institusi/Perusahaan</FormLabel>
|
|
||||||
{/* <Input
|
|
||||||
type="text"
|
|
||||||
value={detail?.userProfilesAdditional?.companyName || ""}
|
|
||||||
placeholder="Nama Institusi/Perusahaan"
|
|
||||||
onChange={field.onChange}
|
|
||||||
/> */}
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
type="text"
|
|
||||||
defaultValue={
|
|
||||||
detail?.userProfilesAdditional?.companyName || ""
|
|
||||||
}
|
|
||||||
placeholder="Nama Institusi/Perusahaan"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<FormLabel>Posisi</FormLabel>
|
|
||||||
{placementRows?.map((row: any) => (
|
|
||||||
<div key={row.index} className="flex items-center gap-2 my-2">
|
|
||||||
<Select
|
|
||||||
value={row.roleId}
|
|
||||||
onValueChange={(e) =>
|
|
||||||
handleSelectionChange(row.index, "roleId", e)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Pilih Role" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{roleSelection?.map((item: any) => (
|
|
||||||
<SelectItem key={item.id} value={String(item.id)}>
|
|
||||||
{item.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{/* <Select
|
|
||||||
onValueChange={(e) =>
|
|
||||||
handleSelectionChange(row.index, "userLevelId", e)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Pilih User Level" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{userLevels?.map((item: any) => (
|
|
||||||
<SelectItem key={item.id} value={String(item.id)}>
|
|
||||||
{item.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select> */}
|
|
||||||
<Select
|
|
||||||
value={row.userLevelId}
|
|
||||||
onValueChange={(e) =>
|
|
||||||
handleSelectionChange(row.index, "userLevelId", e)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Pilih User Level" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="216">MABES POLRI</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{placementRows.length > 1 && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="md"
|
|
||||||
color="destructive"
|
|
||||||
onClick={() => handleRemoveRow(row.index)}
|
|
||||||
>
|
|
||||||
Hapus
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="md"
|
|
||||||
onClick={handleAddRow}
|
|
||||||
disabled={placementRows.length >= 2}
|
|
||||||
>
|
|
||||||
Tambah
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
|
|
||||||
<Button
|
|
||||||
size="md"
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
color="destructive"
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="md"
|
|
||||||
type="submit"
|
|
||||||
color="primary"
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -133,8 +133,8 @@ export default function ContentManagement() {
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<Accordion id="polri" type="single" collapsible className="w-full">
|
<Accordion id="polri" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
KONTEN YANG DISIMPAN OLEH PENGGUNA POLRI INDONESIA
|
KONTEN YANG DISIMPAN OLEH PENGGUNA POLRI INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -161,8 +161,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="2" type="single" collapsible className="w-full">
|
<Accordion id="2" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-2" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-2" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -189,8 +189,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="3" type="single" collapsible className="w-full">
|
<Accordion id="3" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
KONTEN YANG DISIMPAN OLEH PENGGUNA JURNALIS INTERNASIONAL
|
KONTEN YANG DISIMPAN OLEH PENGGUNA JURNALIS INTERNASIONAL
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -217,8 +217,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="4" type="single" collapsible className="w-full">
|
<Accordion id="4" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA POLRI INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA POLRI INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -245,8 +245,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="5" type="single" collapsible className="w-full">
|
<Accordion id="5" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -273,8 +273,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="6" type="single" collapsible className="w-full">
|
<Accordion id="6" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INTERNASIONAL
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INTERNASIONAL
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ export default function EmergencyIssue() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
ANALISA BERKAITAN DENGAN AKUN PELAPOR{" "}
|
ANALISA BERKAITAN DENGAN AKUN PELAPOR{" "}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ export default function FeedbackCenter() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
TICKET PADA FEEDBACK CENTER{" "}
|
TICKET PADA FEEDBACK CENTER{" "}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
PUBLISH JADWAL PRESS CONFERENCE TERBANYAK
|
PUBLISH JADWAL PRESS CONFERENCE TERBANYAK
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -147,8 +147,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
JUMLAH PRODUKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
JUMLAH PRODUKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -180,8 +180,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
TINGKAT INTERAKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
TINGKAT INTERAKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -213,8 +213,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
<AccordionItem value="item-1" className="bg-white w-full">
|
||||||
<AccordionTrigger className="bg-white dark:bg-black">
|
<AccordionTrigger className="bg-white">
|
||||||
AKTIFITAS MEDIA BERKAITAN DENGAN PERS RILIS
|
AKTIFITAS MEDIA BERKAITAN DENGAN PERS RILIS
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -30,27 +30,34 @@ const columns: ColumnDef<any>[] = [
|
||||||
accessorKey: "accountName",
|
accessorKey: "accountName",
|
||||||
header: "Nama",
|
header: "Nama",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.original.mediaBlastAccountName}</span>
|
<span className="normal-case">{row.getValue("accountName")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "accountType",
|
accessorKey: "accountType",
|
||||||
header: "Tipe Akun",
|
header: "Tipe Akun",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.original.mediaBlastAccountType}</span>
|
<span className="normal-case">{row.getValue("accountType")}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "accountCategory",
|
||||||
|
header: "Kategory",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="uppercase">{row.getValue("accountCategory")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "emailAddress",
|
accessorKey: "emailAddress",
|
||||||
header: "Email",
|
header: "Email",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.original.mediaBlastAccountEmail}</span>
|
<span className="normal-case">{row.getValue("emailAddress")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "whatsappNumber",
|
accessorKey: "whatsappNumber",
|
||||||
header: "Whatsapp",
|
header: "Whatsapp",
|
||||||
cell: ({ row }) => <span>{row.original.mediaBlastAccountPhone}</span>,
|
cell: ({ row }) => <span>{row.getValue("whatsappNumber")}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
|
@ -106,7 +113,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/broadcast/campaign-list/account-list/edit/${row.original.mediaBlastAccountId}`}
|
href={`/admin/broadcast/campaign-list/account-list/edit/${row.original.id}`}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
SortingState,
|
SortingState,
|
||||||
|
|
@ -14,6 +15,7 @@ import {
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -22,64 +24,25 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import {
|
import { UserIcon } from "lucide-react";
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
DialogFooter,
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
DialogHeader,
|
import columns from "./column";
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
DialogClose,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
|
||||||
Select as UISelect,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
import { getMediaBlastAccountPage } from "@/service/broadcast/broadcast";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Icon } from "@iconify/react";
|
import { close, loading } from "@/config/swal";
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { X } from "lucide-react";
|
|
||||||
import ReactSelect from "react-select";
|
|
||||||
|
|
||||||
import columns from "./column";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import {
|
|
||||||
getMediaBlastCampaignAccountList,
|
|
||||||
deleteMediaBlastCampaignAccount,
|
|
||||||
saveMediaBlastCampaignAccountBulk,
|
|
||||||
} from "@/service/broadcast/broadcast";
|
|
||||||
import {
|
|
||||||
AdministrationUserList,
|
|
||||||
getUserListAll,
|
|
||||||
} from "@/service/management-user/management-user";
|
|
||||||
import { close, loading, error, success, successCallback } from "@/config/swal";
|
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
// Mock data for available accounts - replace with actual API call
|
|
||||||
const availableAccounts = [
|
|
||||||
{ id: "1", accountName: "Account 1", category: "polri" },
|
|
||||||
{ id: "2", accountName: "Account 2", category: "jurnalis" },
|
|
||||||
{ id: "3", accountName: "Account 3", category: "umum" },
|
|
||||||
{ id: "4", accountName: "Account 4", category: "ksp" },
|
|
||||||
{ id: "5", accountName: "Account 5", category: "polri" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const AccountListTable = () => {
|
const AccountListTable = () => {
|
||||||
const params = useParams();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const campaignId = params?.id as string;
|
|
||||||
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
|
@ -93,19 +56,10 @@ const AccountListTable = () => {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [filtered, setFiltered] = React.useState<string[]>([]);
|
const [filtered, setFiltered] = React.useState<string[]>([]);
|
||||||
|
|
||||||
// --- state utk Dialog Pilih Akun ---
|
|
||||||
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
|
||||||
const [accountCategory, setAccountCategory] = React.useState<string>("");
|
|
||||||
const [selectedAccount, setSelectedAccount] = React.useState<any[]>([]);
|
|
||||||
const [selectedCategory, setSelectedCategory] = React.useState<string>("");
|
|
||||||
const [availableAccountsList, setAvailableAccountsList] =
|
|
||||||
React.useState<any[]>(availableAccounts);
|
|
||||||
const [usersList, setUsersList] = React.useState<any[]>([]);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -129,24 +83,24 @@ const AccountListTable = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const pageFromUrl = searchParams?.get("page");
|
const pageFromUrl = searchParams?.get("page");
|
||||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, filtered]);
|
}, [page]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
loading();
|
loading();
|
||||||
const res = await getMediaBlastCampaignAccountList(
|
const res = await getMediaBlastAccountPage(
|
||||||
page - 1,
|
page - 1,
|
||||||
filtered ? filtered.join(",") : "",
|
filtered ? filtered.join(",") : ""
|
||||||
campaignId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content || [];
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * 10 + index + 1;
|
item.no = (page - 1) * 10 + index + 1;
|
||||||
});
|
});
|
||||||
|
|
@ -155,349 +109,42 @@ const AccountListTable = () => {
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
close();
|
close();
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error("Error fetching tasks:", err);
|
console.error("Error fetching tasks:", error);
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API helpers ---
|
|
||||||
async function doDeleteAccount(id: string) {
|
|
||||||
loading();
|
|
||||||
const response = await deleteMediaBlastCampaignAccount(id);
|
|
||||||
close();
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveCampaignAccount() {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
|
|
||||||
if (accountCategory === "all-account") {
|
|
||||||
// Handle all accounts - send only campaignId and category "all"
|
|
||||||
const request = {
|
|
||||||
mediaBlastCampaignId: campaignId,
|
|
||||||
mediaBlastAccountCategory: "all",
|
|
||||||
};
|
|
||||||
const response = await saveMediaBlastCampaignAccountBulk(request);
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (accountCategory === "kategori" && selectedCategory) {
|
|
||||||
// Handle category selection - send campaignId and role-based category
|
|
||||||
let roleId = "";
|
|
||||||
switch (selectedCategory) {
|
|
||||||
case "umum":
|
|
||||||
roleId = "5";
|
|
||||||
break;
|
|
||||||
case "jurnalis":
|
|
||||||
roleId = "6";
|
|
||||||
break;
|
|
||||||
case "polri":
|
|
||||||
roleId = "7";
|
|
||||||
break;
|
|
||||||
case "ksp":
|
|
||||||
roleId = "8";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
roleId = "5";
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
mediaBlastCampaignId: campaignId,
|
|
||||||
mediaBlastAccountCategory: `role-${roleId}`,
|
|
||||||
};
|
|
||||||
const response = await saveMediaBlastCampaignAccountBulk(request);
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (accountCategory === "custom") {
|
|
||||||
// Handle custom selection - send campaignId and selected user IDs
|
|
||||||
const request = {
|
|
||||||
mediaBlastCampaignId: campaignId,
|
|
||||||
mediaBlastAccountIds: selectedAccount.map((acc) => acc.id),
|
|
||||||
};
|
|
||||||
const response = await saveMediaBlastCampaignAccountBulk(request);
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close();
|
|
||||||
successCallback("Akun berhasil ditambahkan ke campaign!");
|
|
||||||
resetDialogState();
|
|
||||||
fetchData();
|
|
||||||
} catch (err) {
|
|
||||||
close();
|
|
||||||
error("Terjadi kesalahan saat menyimpan akun");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetDialogState = () => {
|
|
||||||
setAccountCategory("");
|
|
||||||
setSelectedAccount([]);
|
|
||||||
setSelectedCategory("");
|
|
||||||
setUsersList([]);
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchUsersList = async () => {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
const response = await getUserListAll();
|
|
||||||
|
|
||||||
if (response?.data?.data?.content) {
|
|
||||||
setUsersList(response.data.data.content);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
} catch (err) {
|
|
||||||
close();
|
|
||||||
error("Terjadi kesalahan saat mengambil daftar user");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilter = (id: string, checked: boolean) => {
|
const handleFilter = (id: string, checked: boolean) => {
|
||||||
let temp = [...filtered];
|
let temp = [...filtered];
|
||||||
if (checked) temp = [...temp, id];
|
if (checked) {
|
||||||
else temp = temp.filter((a) => a !== id);
|
temp = [...temp, id];
|
||||||
setFiltered(temp);
|
} else {
|
||||||
};
|
temp = temp.filter((a) => a !== id);
|
||||||
|
|
||||||
const removeSelectedAccount = (accountId: string) => {
|
|
||||||
setSelectedAccount(selectedAccount.filter((acc) => acc.id !== accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFilteredAccounts = () => {
|
|
||||||
if (accountCategory === "kategori" && selectedCategory) {
|
|
||||||
return availableAccountsList.filter(
|
|
||||||
(acc) => acc.category === selectedCategory
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return availableAccountsList;
|
setFiltered(temp);
|
||||||
|
console.log("sss", temp);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-3 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Daftar Akun</p>
|
<p className="text-xl font-medium text-default-900">Daftar Akun</p>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
{/* === Dialog Pilih Akun === */}
|
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button size="sm" className="text-sm">
|
|
||||||
<Icon icon="tdesign:user-add-filled" className="mr-2" />
|
|
||||||
Pilih Akun
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent
|
|
||||||
size="md"
|
|
||||||
className="max-w-xl max-h-[80vh] overflow-y-auto"
|
|
||||||
>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Pilih Akun Untuk Campaign Ini</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-4 my-3">
|
|
||||||
<RadioGroup
|
|
||||||
value={accountCategory}
|
|
||||||
onValueChange={(val) => {
|
|
||||||
setAccountCategory(val);
|
|
||||||
setSelectedAccount([]);
|
|
||||||
setSelectedCategory("");
|
|
||||||
if (val === "custom") {
|
|
||||||
fetchUsersList();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="flex space-x-6"
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="all-account" id="all-account" />
|
|
||||||
<Label htmlFor="all-account">Semua Akun</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="kategori" id="kategori" />
|
|
||||||
<Label htmlFor="kategori">Kategori</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="custom" id="custom" />
|
|
||||||
<Label htmlFor="custom">Kustom</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
{/* Category Selection */}
|
|
||||||
{accountCategory === "kategori" && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Pilih Kategori:</Label>
|
|
||||||
<UISelect onValueChange={(val) => setSelectedCategory(val)}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Pilih kategori" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="umum">Umum</SelectItem>
|
|
||||||
<SelectItem value="polri">Polri</SelectItem>
|
|
||||||
<SelectItem value="ksp">KSP</SelectItem>
|
|
||||||
<SelectItem value="jurnalis">Jurnalis</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</UISelect>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Custom Account Selection */}
|
|
||||||
{accountCategory === "custom" && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label>Pilih User:</Label>
|
|
||||||
<ReactSelect
|
|
||||||
isMulti
|
|
||||||
options={usersList.map((user: any) => ({
|
|
||||||
value: user.id,
|
|
||||||
label: `${user.fullname} (${user.role?.name})`,
|
|
||||||
user: user,
|
|
||||||
}))}
|
|
||||||
value={selectedAccount.map((acc: any) => ({
|
|
||||||
value: acc.id,
|
|
||||||
label: `${acc.fullname} (${acc.role?.name})`,
|
|
||||||
user: acc,
|
|
||||||
}))}
|
|
||||||
onChange={(selectedOptions: any) => {
|
|
||||||
const selectedUsers = selectedOptions
|
|
||||||
? selectedOptions.map((option: any) => option.user)
|
|
||||||
: [];
|
|
||||||
setSelectedAccount(selectedUsers);
|
|
||||||
}}
|
|
||||||
placeholder="Cari dan pilih user..."
|
|
||||||
noOptionsMessage={() => "Tidak ada user ditemukan"}
|
|
||||||
loadingMessage={() => "Memuat..."}
|
|
||||||
isSearchable={true}
|
|
||||||
isClearable={true}
|
|
||||||
className="react-select"
|
|
||||||
classNamePrefix="select"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Selected Accounts Display */}
|
|
||||||
{selectedAccount.length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>User Terpilih ({selectedAccount.length}):</Label>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{selectedAccount.map((acc) => (
|
|
||||||
<Badge
|
|
||||||
key={acc.id}
|
|
||||||
className="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
{acc.fullname}
|
|
||||||
<X
|
|
||||||
className="h-3 w-3 cursor-pointer"
|
|
||||||
onClick={() => removeSelectedAccount(acc.id)}
|
|
||||||
/>
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* All Accounts Info */}
|
|
||||||
{accountCategory === "all-account" && (
|
|
||||||
<div className="p-3 bg-blue-50 rounded-md">
|
|
||||||
<p className="text-sm text-blue-700">
|
|
||||||
Semua akun akan ditambahkan ke campaign ini.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Category Accounts Info */}
|
|
||||||
{accountCategory === "kategori" && selectedCategory && (
|
|
||||||
<div className="p-3 bg-green-50 rounded-md">
|
|
||||||
<p className="text-sm text-green-700">
|
|
||||||
Semua akun dengan role "{selectedCategory.toUpperCase()}"
|
|
||||||
akan ditambahkan.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Custom Selection Info */}
|
|
||||||
{accountCategory === "custom" && (
|
|
||||||
<div className="p-3 bg-purple-50 rounded-md">
|
|
||||||
<p className="text-sm text-purple-700">
|
|
||||||
{selectedAccount.length} user terpilih akan ditambahkan ke
|
|
||||||
campaign ini.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
onClick={saveCampaignAccount}
|
|
||||||
disabled={
|
|
||||||
!accountCategory ||
|
|
||||||
(accountCategory === "custom" &&
|
|
||||||
selectedAccount.length < 1) ||
|
|
||||||
(accountCategory === "kategori" && !selectedCategory)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button variant="outline" onClick={resetDialogState}>
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* === Filter Akun === */}
|
|
||||||
<div className="flex flex-row justify-end">
|
|
||||||
{/* <div className="flex flex-row gap-4">
|
|
||||||
<Link href="/admin/broadcast/campaign-list/account-list/create">
|
<Link href="/admin/broadcast/campaign-list/account-list/create">
|
||||||
<Button variant="default" className="bg-[#3f37c9] gap-2">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
<span>
|
<Icon icon="tdesign:user-add-filled" />
|
||||||
<svg
|
Tambah Akun
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="22"
|
|
||||||
height="22"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M17 13h-4v4h-2v-4H7v-2h4V7h2v4h4m2-8H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
Tambahkan Akun
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button variant="default" className="bg-[#3f37c9] gap-2">
|
<Link href="/admin/broadcast/campaign-list/import">
|
||||||
<span>
|
<Button color="success" size="md" className="text-sm">
|
||||||
<svg
|
<UserIcon />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<g fill="none">
|
|
||||||
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M12 2v6.5a1.5 1.5 0 0 0 1.5 1.5H20v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1h3.414l-1.121 1.121a1 1 0 1 0 1.414 1.415l2.829-2.829a1 1 0 0 0 0-1.414l-2.829-2.828a1 1 0 1 0-1.414 1.414L7.414 17H4V4a2 2 0 0 1 2-2zM4 17v2H3a1 1 0 1 1 0-2zM14 2.043a2 2 0 0 1 1 .543L19.414 7a2 2 0 0 1 .543 1H14z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
Import Akun
|
Import Akun
|
||||||
</Button>
|
</Button>
|
||||||
</div> */}
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button size="md" variant="outline">
|
<Button size="md" variant="outline">
|
||||||
|
|
@ -516,28 +163,65 @@ const AccountListTable = () => {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
||||||
{["polri", "jurnalis", "umum", "ksp"].map((cat) => (
|
<div className="flex items-center space-x-2">
|
||||||
<div key={cat} className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={cat}
|
id="accepted"
|
||||||
checked={filtered.includes(cat)}
|
checked={filtered.includes("polri")}
|
||||||
onCheckedChange={(e) => handleFilter(cat, Boolean(e))}
|
onCheckedChange={(e) => handleFilter("polri", Boolean(e))}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={cat}
|
htmlFor="accepted"
|
||||||
className="text-xs font-medium leading-none"
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
>
|
>
|
||||||
{cat.toUpperCase()}
|
POLRI
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={filtered.includes("jurnalis")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleFilter("jurnalis", Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
JURNALIS
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={filtered.includes("umum")}
|
||||||
|
onCheckedChange={(e) => handleFilter("umum", Boolean(e))}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
UMUM
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={filtered.includes("ksp")}
|
||||||
|
onCheckedChange={(e) => handleFilter("ksp", Boolean(e))}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
KSP
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* === Table Data === */}
|
|
||||||
<Table className="overflow-hidden">
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
|
@ -579,7 +263,6 @@ const AccountListTable = () => {
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<TablePagination
|
<TablePagination
|
||||||
table={table}
|
table={table}
|
||||||
totalData={totalData}
|
totalData={totalData}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
|
|
@ -13,80 +14,55 @@ import {
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { getOnlyDate } from "@/utils/globals";
|
||||||
import {
|
import {
|
||||||
getMediaBlastCampaignPage,
|
|
||||||
saveMediaBlastAccount,
|
saveMediaBlastAccount,
|
||||||
|
saveMediaBlastCampaign,
|
||||||
} from "@/service/broadcast/broadcast";
|
} from "@/service/broadcast/broadcast";
|
||||||
import { error } from "@/config/swal";
|
import { error } from "@/config/swal";
|
||||||
import { useRouter } from "@/i18n/routing";
|
import { useRouter } from "@/i18n/routing";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { useEffect, useState } from "react";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
|
||||||
// ----------------------------
|
|
||||||
// ZOD SCHEMA (dinamis)
|
|
||||||
// ----------------------------
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
name: z.string({ required_error: "Required" }),
|
name: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
accountType: z
|
accountType: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, "Pilih minimal satu tipe akun"),
|
.refine((value) => value.some((item) => item), {
|
||||||
|
message: "Required",
|
||||||
email: z.string().optional(),
|
}),
|
||||||
whatsapp: z.string().optional(),
|
accountCategory: z.enum(["polri", "jurnalis", "umumu", "ksp"], {
|
||||||
|
required_error: "Required",
|
||||||
campaignId: z.string({ required_error: "Required" }),
|
}),
|
||||||
}).refine(
|
email: z.string({
|
||||||
(data) => {
|
required_error: "Required",
|
||||||
if (data.accountType.includes("email") && !data.email) return false;
|
}),
|
||||||
return true;
|
whatsapp: z.string({
|
||||||
},
|
required_error: "Required",
|
||||||
{ message: "Email wajib diisi", path: ["email"] }
|
}),
|
||||||
).refine(
|
});
|
||||||
(data) => {
|
|
||||||
if (data.accountType.includes("wa") && !data.whatsapp) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
{ message: "Whatsapp wajib diisi", path: ["whatsapp"] }
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------
|
|
||||||
// COMPONENT
|
|
||||||
// ----------------------------
|
|
||||||
|
|
||||||
export default function CreateAccountForBroadcast() {
|
export default function CreateAccountForBroadcast() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
defaultValues: {
|
defaultValues: { accountType: [] },
|
||||||
accountType: [],
|
|
||||||
email: "",
|
|
||||||
whatsapp: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedTypes = form.watch("accountType");
|
|
||||||
const [campaigns, setCampaigns] = useState<any[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchCampaignList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function fetchCampaignList() {
|
|
||||||
try {
|
|
||||||
const res = await getMediaBlastCampaignPage(0);
|
|
||||||
setCampaigns(res?.data?.data?.content ?? []);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error fetch campaign:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Simpan Data",
|
title: "Simpan Data",
|
||||||
|
|
@ -109,8 +85,10 @@ export default function CreateAccountForBroadcast() {
|
||||||
icon: "success",
|
icon: "success",
|
||||||
confirmButtonColor: "#3085d6",
|
confirmButtonColor: "#3085d6",
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
}).then(() => {
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
router.push("/admin/broadcast/campaign-list/account-list");
|
router.push("/admin/broadcast/campaign-list/account-list");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,21 +96,20 @@ export default function CreateAccountForBroadcast() {
|
||||||
const reqData = {
|
const reqData = {
|
||||||
accountName: data.name,
|
accountName: data.name,
|
||||||
accountType: data.accountType.join(","),
|
accountType: data.accountType.join(","),
|
||||||
emailAddress: data.email ?? "",
|
accountCategory: data.accountCategory,
|
||||||
whatsappNumber: data.whatsapp ?? "",
|
emailAddress: data.email,
|
||||||
campaignId: data.campaignId,
|
whatsappNumber: data.whatsapp,
|
||||||
};
|
};
|
||||||
|
console.log("data", data);
|
||||||
console.log("REQ:", reqData);
|
|
||||||
|
|
||||||
const response = await saveMediaBlastAccount(reqData);
|
const response = await saveMediaBlastAccount(reqData);
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response.message);
|
error(response.message);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
successSubmit();
|
successSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
|
|
@ -141,9 +118,7 @@ export default function CreateAccountForBroadcast() {
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white rounded-sm p-4"
|
className="space-y-3 bg-white rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="font-semibold">Account</p>
|
<p className="fonnt-semibold">Account</p>
|
||||||
|
|
||||||
{/* NAMA */}
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
|
|
@ -155,125 +130,172 @@ export default function CreateAccountForBroadcast() {
|
||||||
placeholder="Masukkan nama"
|
placeholder="Masukkan nama"
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* CHECKBOX TIPE AKUN */}
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accountType"
|
name="accountType"
|
||||||
render={({ field }) => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Tipe Akun</FormLabel>
|
<FormLabel>Tipe Akun</FormLabel>
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row gap-2">
|
||||||
{/* WA */}
|
{" "}
|
||||||
<div className="flex items-center gap-2">
|
<FormField
|
||||||
|
key="wa"
|
||||||
|
control={form.control}
|
||||||
|
name="accountType"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
key="wa"
|
||||||
|
className="flex flex-row items-start space-x-3 space-y-0"
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value.includes("wa")}
|
checked={field.value?.includes("wa")}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) => {
|
||||||
checked
|
return checked
|
||||||
? field.onChange([...field.value, "wa"])
|
? field.onChange([...field.value, "wa"])
|
||||||
: field.onChange(field.value.filter((v) => v !== "wa"))
|
: field.onChange(
|
||||||
}
|
field.value?.filter(
|
||||||
|
(value) => value !== "wa"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<label>Whatsapp</label>
|
</FormControl>
|
||||||
</div>
|
<FormLabel className="font-normal">
|
||||||
|
Whatsapp
|
||||||
{/* EMAIL */}
|
</FormLabel>
|
||||||
<div className="flex items-center gap-2">
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
key="email"
|
||||||
|
control={form.control}
|
||||||
|
name="accountType"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
key="email"
|
||||||
|
className="flex flex-row items-start space-x-3 space-y-0"
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value.includes("email")}
|
checked={field.value?.includes("email")}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) => {
|
||||||
checked
|
return checked
|
||||||
? field.onChange([...field.value, "email"])
|
? field.onChange([...field.value, "email"])
|
||||||
: field.onChange(
|
: field.onChange(
|
||||||
field.value.filter((v) => v !== "email")
|
field.value?.filter(
|
||||||
|
(value) => value !== "email"
|
||||||
)
|
)
|
||||||
}
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">Email</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<label>Email</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* FORM WHATSAPP */}
|
|
||||||
{selectedTypes.includes("wa") && (
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="whatsapp"
|
name="accountCategory"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="space-y-3">
|
||||||
<FormLabel>Whatsapp</FormLabel>
|
<FormLabel>Kategori</FormLabel>
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="Masukkan nomor Whatsapp"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* FORM EMAIL */}
|
|
||||||
{selectedTypes.includes("email") && (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
<Input
|
|
||||||
type="email"
|
|
||||||
placeholder="Masukkan email"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* CAMPAIGN */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="campaignId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Campaign</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<select
|
<RadioGroup
|
||||||
className="w-full border rounded-md p-2 text-sm"
|
onValueChange={field.onChange}
|
||||||
value={field.value}
|
defaultValue={field.value}
|
||||||
onChange={field.onChange}
|
className="flex flex-row gap-2"
|
||||||
>
|
>
|
||||||
<option value="" className="text-slate-400">
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
Pilih campaign
|
<FormControl>
|
||||||
</option>
|
<RadioGroupItem value="polri" />
|
||||||
|
</FormControl>
|
||||||
{campaigns.map((c: any) => (
|
<FormLabel className="font-normal">POLRI</FormLabel>
|
||||||
<option key={c.id} value={c.id}>
|
</FormItem>
|
||||||
{c.title || `Campaign ${c.id}`}
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
</option>
|
<FormControl>
|
||||||
))}
|
<RadioGroupItem value="jurnalis" />
|
||||||
</select>
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">JURNALIS</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="umum" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">UMUM</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="ksp" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">KSP</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nama</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={field.value}
|
||||||
|
placeholder="Masukkan email"
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* BUTTON */}
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="whatsapp"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nama</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={field.value}
|
||||||
|
placeholder="Masukkan whatsapp"
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="flex flex-row gap-2 mt-4 pt-4">
|
<div className="flex flex-row gap-2 mt-4 pt-4">
|
||||||
<Button type="button" variant="outline" color="destructive">
|
<Button
|
||||||
|
size="md"
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
color="destructive"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" color="primary">
|
<Button size="md" type="submit" color="primary" className="text-xs">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -282,380 +304,3 @@ export default function CreateAccountForBroadcast() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// "use client";
|
|
||||||
// import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
// import { z } from "zod";
|
|
||||||
// import { useForm } from "react-hook-form";
|
|
||||||
// import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
// import {
|
|
||||||
// Form,
|
|
||||||
// FormControl,
|
|
||||||
// FormField,
|
|
||||||
// FormItem,
|
|
||||||
// FormLabel,
|
|
||||||
// FormMessage,
|
|
||||||
// } from "@/components/ui/form";
|
|
||||||
// import withReactContent from "sweetalert2-react-content";
|
|
||||||
// import Swal from "sweetalert2";
|
|
||||||
// import { Input } from "@/components/ui/input";
|
|
||||||
// import { Button } from "@/components/ui/button";
|
|
||||||
// import {
|
|
||||||
// getMediaBlastCampaignPage,
|
|
||||||
// saveMediaBlastAccount,
|
|
||||||
// } from "@/service/broadcast/broadcast";
|
|
||||||
// import { error } from "@/config/swal";
|
|
||||||
// import { useRouter } from "@/i18n/routing";
|
|
||||||
// import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
// import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
// // const FormSchema = z.object({
|
|
||||||
// // name: z.string({
|
|
||||||
// // required_error: "Required",
|
|
||||||
// // }),
|
|
||||||
// // accountType: z
|
|
||||||
// // .array(z.string())
|
|
||||||
// // .refine((value) => value.some((item) => item), {
|
|
||||||
// // message: "Required",
|
|
||||||
// // }),
|
|
||||||
// // accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
|
||||||
// // required_error: "Required",
|
|
||||||
// // }),
|
|
||||||
// // email: z.string({
|
|
||||||
// // required_error: "Required",
|
|
||||||
// // }),
|
|
||||||
// // whatsapp: z.string({
|
|
||||||
// // required_error: "Required",
|
|
||||||
// // }),
|
|
||||||
// // campaignId: z.string({ required_error: "Required" }),
|
|
||||||
// // });
|
|
||||||
// const FormSchema = z
|
|
||||||
// .object({
|
|
||||||
// name: z.string().min(1, "Required"),
|
|
||||||
|
|
||||||
// accountType: z.array(z.string()).refine((value) => value.length > 0, {
|
|
||||||
// message: "Pilih minimal satu tipe akun",
|
|
||||||
// }),
|
|
||||||
|
|
||||||
// accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
|
|
||||||
// email: z.string().optional(),
|
|
||||||
// whatsapp: z.string().optional(),
|
|
||||||
|
|
||||||
// campaignId: z.string().min(1, "Required"),
|
|
||||||
// })
|
|
||||||
// .refine(
|
|
||||||
// (data) => {
|
|
||||||
// if (data.accountType.includes("email")) {
|
|
||||||
// return !!data.email && data.email.trim() !== "";
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// },
|
|
||||||
// { path: ["email"], message: "Email wajib diisi" }
|
|
||||||
// )
|
|
||||||
// .refine(
|
|
||||||
// (data) => {
|
|
||||||
// if (data.accountType.includes("wa")) {
|
|
||||||
// return !!data.whatsapp && data.whatsapp.trim() !== "";
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// },
|
|
||||||
// { path: ["whatsapp"], message: "Whatsapp wajib diisi" }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// export default function CreateAccountForBroadcast() {
|
|
||||||
// const MySwal = withReactContent(Swal);
|
|
||||||
// const router = useRouter();
|
|
||||||
// const form = useForm<z.infer<typeof FormSchema>>({
|
|
||||||
// resolver: zodResolver(FormSchema),
|
|
||||||
// defaultValues: { accountType: [] },
|
|
||||||
// });
|
|
||||||
// const selectedTypes = form.watch("accountType");
|
|
||||||
// const [campaigns, setCampaigns] = useState<any[]>([]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// fetchCampaignList();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// async function fetchCampaignList() {
|
|
||||||
// try {
|
|
||||||
// const res = await getMediaBlastCampaignPage(0);
|
|
||||||
// setCampaigns(res?.data?.data?.content ?? []);
|
|
||||||
// } catch (e) {
|
|
||||||
// console.log("Error fetch campaign:", e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Simpan Data",
|
|
||||||
// text: "Apakah Anda yakin ingin menyimpan data ini?",
|
|
||||||
// icon: "warning",
|
|
||||||
// showCancelButton: true,
|
|
||||||
// cancelButtonColor: "#d33",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// confirmButtonText: "Simpan",
|
|
||||||
// }).then((result) => {
|
|
||||||
// if (result.isConfirmed) {
|
|
||||||
// save(data);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// function successSubmit() {
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Sukses",
|
|
||||||
// icon: "success",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// confirmButtonText: "OK",
|
|
||||||
// }).then((result) => {
|
|
||||||
// if (result.isConfirmed) {
|
|
||||||
// router.push("/admin/broadcast/campaign-list/account-list");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const save = async (data: z.infer<typeof FormSchema>) => {
|
|
||||||
// const reqData = {
|
|
||||||
// accountName: data.name,
|
|
||||||
// accountType: data.accountType.join(","),
|
|
||||||
// accountCategory: data.accountCategory,
|
|
||||||
// emailAddress: data.email ?? "",
|
|
||||||
// whatsappNumber: data.whatsapp ?? "",
|
|
||||||
// campaignId: data.campaignId,
|
|
||||||
// };
|
|
||||||
// console.log("data", data);
|
|
||||||
|
|
||||||
// const response = await saveMediaBlastAccount(reqData);
|
|
||||||
// if (response?.error) {
|
|
||||||
// error(response.message);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// successSubmit();
|
|
||||||
// };
|
|
||||||
// return (
|
|
||||||
// <div>
|
|
||||||
// <SiteBreadcrumb />
|
|
||||||
// <Form {...form}>
|
|
||||||
// <form
|
|
||||||
// onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
// className="space-y-3 bg-white rounded-sm p-4"
|
|
||||||
// >
|
|
||||||
// <p className="fonnt-semibold">Account</p>
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="name"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Nama</FormLabel>
|
|
||||||
// <Input
|
|
||||||
// value={field.value}
|
|
||||||
// placeholder="Masukkan nama"
|
|
||||||
// onChange={field.onChange}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="accountType"
|
|
||||||
// render={() => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Tipe Akun</FormLabel>
|
|
||||||
// <div className="flex flex-row gap-2">
|
|
||||||
// {" "}
|
|
||||||
// <FormField
|
|
||||||
// key="wa"
|
|
||||||
// control={form.control}
|
|
||||||
// name="accountType"
|
|
||||||
// render={({ field }) => {
|
|
||||||
// return (
|
|
||||||
// <FormItem
|
|
||||||
// key="wa"
|
|
||||||
// className="flex flex-row items-start space-x-3 space-y-0"
|
|
||||||
// >
|
|
||||||
// <FormControl>
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value?.includes("wa")}
|
|
||||||
// onCheckedChange={(checked) => {
|
|
||||||
// return checked
|
|
||||||
// ? field.onChange([...field.value, "wa"])
|
|
||||||
// : field.onChange(
|
|
||||||
// field.value?.filter(
|
|
||||||
// (value) => value !== "wa"
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">
|
|
||||||
// Whatsapp
|
|
||||||
// </FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// );
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// <FormField
|
|
||||||
// key="email"
|
|
||||||
// control={form.control}
|
|
||||||
// name="accountType"
|
|
||||||
// render={({ field }) => {
|
|
||||||
// return (
|
|
||||||
// <FormItem
|
|
||||||
// key="email"
|
|
||||||
// className="flex flex-row items-start space-x-3 space-y-0"
|
|
||||||
// >
|
|
||||||
// <FormControl>
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value?.includes("email")}
|
|
||||||
// onCheckedChange={(checked) => {
|
|
||||||
// return checked
|
|
||||||
// ? field.onChange([...field.value, "email"])
|
|
||||||
// : field.onChange(
|
|
||||||
// field.value?.filter(
|
|
||||||
// (value) => value !== "email"
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">Email</FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// );
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// {/* <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="accountCategory"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem className="space-y-3">
|
|
||||||
// <FormLabel>Kategori</FormLabel>
|
|
||||||
// <FormControl>
|
|
||||||
// <RadioGroup
|
|
||||||
// onValueChange={field.onChange}
|
|
||||||
// defaultValue={field.value}
|
|
||||||
// className="flex flex-row gap-2"
|
|
||||||
// >
|
|
||||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
|
||||||
// <FormControl>
|
|
||||||
// <RadioGroupItem value="polri" />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">POLRI</FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
|
||||||
// <FormControl>
|
|
||||||
// <RadioGroupItem value="jurnalis" />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">JURNALIS</FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
|
||||||
// <FormControl>
|
|
||||||
// <RadioGroupItem value="umum" />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">UMUM</FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
|
||||||
// <FormControl>
|
|
||||||
// <RadioGroupItem value="ksp" />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel className="font-normal">KSP</FormLabel>
|
|
||||||
// </FormItem>
|
|
||||||
// </RadioGroup>
|
|
||||||
// </FormControl>
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// /> */}
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="email"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Email</FormLabel>
|
|
||||||
// <Input
|
|
||||||
// type="email"
|
|
||||||
// value={field.value}
|
|
||||||
// placeholder="Masukkan email"
|
|
||||||
// onChange={field.onChange}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="whatsapp"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Nama</FormLabel>
|
|
||||||
// <Input
|
|
||||||
// type="number"
|
|
||||||
// value={field.value}
|
|
||||||
// placeholder="Masukkan whatsapp"
|
|
||||||
// onChange={field.onChange}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="campaignId"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Campaign</FormLabel>
|
|
||||||
// <FormControl>
|
|
||||||
// <select
|
|
||||||
// className="w-full border rounded-md p-2"
|
|
||||||
// value={field.value}
|
|
||||||
// onChange={field.onChange}
|
|
||||||
// >
|
|
||||||
// <option value="" className="text-slate-400">
|
|
||||||
// Pilih campaign
|
|
||||||
// </option>
|
|
||||||
|
|
||||||
// {campaigns.map((c: any) => (
|
|
||||||
// <option key={c.id} value={c.id}>
|
|
||||||
// {c.title || `Campaign ${c.id}`}
|
|
||||||
// </option>
|
|
||||||
// ))}
|
|
||||||
// </select>
|
|
||||||
// </FormControl>
|
|
||||||
// <FormMessage />
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
// <div className="flex flex-row gap-2 mt-4 pt-4">
|
|
||||||
// <Button
|
|
||||||
// size="md"
|
|
||||||
// type="button"
|
|
||||||
// variant="outline"
|
|
||||||
// color="destructive"
|
|
||||||
// className="text-xs"
|
|
||||||
// >
|
|
||||||
// Cancel
|
|
||||||
// </Button>
|
|
||||||
// <Button size="md" type="submit" color="primary" className="text-xs">
|
|
||||||
// Submit
|
|
||||||
// </Button>
|
|
||||||
// </div>
|
|
||||||
// </form>
|
|
||||||
// </Form>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -37,74 +37,46 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import {
|
|
||||||
getUserById,
|
|
||||||
saveUserInternal,
|
|
||||||
} from "@/service/management-user/management-user";
|
|
||||||
|
|
||||||
// const FormSchema = z.object({
|
|
||||||
// fullname: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// // accountType: z
|
|
||||||
// // .array(z.string())
|
|
||||||
// // .refine((value) => value.some((item) => item), {
|
|
||||||
// // message: "Required",
|
|
||||||
// // }),
|
|
||||||
// // accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
|
||||||
// // required_error: "Required",
|
|
||||||
// // }),
|
|
||||||
// email: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// phoneNumber: z.string({
|
|
||||||
// required_error: "Required",
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
fullname: z.string({ required_error: "Required" }),
|
name: z.string({
|
||||||
email: z.string({ required_error: "Required" }),
|
required_error: "Required",
|
||||||
phoneNumber: z.string({ required_error: "Required" }),
|
}),
|
||||||
username: z.string().optional(),
|
accountType: z
|
||||||
role: z.string().optional(),
|
.array(z.string())
|
||||||
level: z.string().optional(),
|
.refine((value) => value.some((item) => item), {
|
||||||
nrp: z.string().optional(),
|
message: "Required",
|
||||||
address: z.string().optional(),
|
}),
|
||||||
password: z.string().optional(),
|
accountCategory: z.enum(["polri", "jurnalis", "umumu", "ksp"], {
|
||||||
confirmPassword: z.string().optional(),
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
email: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
whatsapp: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function EditAccountForBroadcast() {
|
export default function EditAccountForBroadcast() {
|
||||||
const id = useParams()?.id;
|
const id = useParams()?.id;
|
||||||
|
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
// defaultValues: { accountType: [] },
|
defaultValues: { accountType: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getDetailData() {
|
async function getDetailData() {
|
||||||
const response = await getUserById(String(id));
|
const response = await getMediaBlastAccount(String(id));
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
console.log("Response full:", response);
|
console.log("new", details);
|
||||||
form.setValue("fullname", details?.fullname);
|
form.setValue("name", details.accountName);
|
||||||
form.setValue("username", details?.username);
|
form.setValue("email", details?.emailAddress);
|
||||||
form.setValue("phoneNumber", details?.phoneNumber);
|
form.setValue("whatsapp", details?.whatsappNumber);
|
||||||
// form.setValue("nrp", details?.memberIdentity);
|
form.setValue("accountCategory", details?.accountCategory);
|
||||||
// form.setValue("address", details?.address);
|
form.setValue("accountType", details?.accountType.split(","));
|
||||||
form.setValue("email", details?.email);
|
|
||||||
form.setValue("role", details?.role?.code);
|
|
||||||
// form.setValue("level", String(details?.userLevelId));
|
|
||||||
// form.setValue("name", details?.accountName);
|
|
||||||
// form.setValue("fullname", details?.fullname);
|
|
||||||
// form.setValue("email", details?.email);
|
|
||||||
// form.setValue("phoneNumber", details?.phoneNumber);
|
|
||||||
// form.setValue("whatsapp", details?.whatsappNumber);
|
|
||||||
// form.setValue("accountCategory", details?.accountCategory);
|
|
||||||
// form.setValue("accountType", details?.accountType.split(","));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDetailData();
|
getDetailData();
|
||||||
|
|
@ -134,32 +106,23 @@ export default function EditAccountForBroadcast() {
|
||||||
confirmButtonText: "OK",
|
confirmButtonText: "OK",
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
router.push("/admin/broadcast/campaign-list");
|
router.push("/admin/broadcast/campaign-list/account-list");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const save = async (data: z.infer<typeof FormSchema>) => {
|
const save = async (data: z.infer<typeof FormSchema>) => {
|
||||||
const reqData = {
|
const reqData = {
|
||||||
id: Number(id),
|
id: String(id),
|
||||||
// accountName: data.fullname,
|
accountName: data.name,
|
||||||
// accountType: data.accountType.join(","),
|
accountType: data.accountType.join(","),
|
||||||
// accountCategory: data.accountCategory,
|
accountCategory: data.accountCategory,
|
||||||
// emailAddress: data.email,
|
emailAddress: data.email,
|
||||||
phoneNumber: data.phoneNumber,
|
whatsappNumber: data.whatsapp,
|
||||||
firstName: data.fullname,
|
|
||||||
username: data.username,
|
|
||||||
roleId: data.role,
|
|
||||||
// userLevelId: Number(data.level),
|
|
||||||
// memberIdentity: data.nrp,
|
|
||||||
// address: data.address,
|
|
||||||
email: data.email,
|
|
||||||
isDefault: false,
|
|
||||||
isAdmin: true,
|
|
||||||
};
|
};
|
||||||
console.log("data", data);
|
console.log("data", data);
|
||||||
|
|
||||||
const response = await saveUserInternal(reqData);
|
const response = await saveMediaBlastAccount(reqData);
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response.message);
|
error(response.message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -178,7 +141,7 @@ export default function EditAccountForBroadcast() {
|
||||||
<p className="fonnt-semibold">Account</p>
|
<p className="fonnt-semibold">Account</p>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="fullname"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Nama</FormLabel>
|
<FormLabel>Nama</FormLabel>
|
||||||
|
|
@ -192,7 +155,7 @@ export default function EditAccountForBroadcast() {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* <FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accountType"
|
name="accountType"
|
||||||
render={() => (
|
render={() => (
|
||||||
|
|
@ -264,8 +227,8 @@ export default function EditAccountForBroadcast() {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
{/* <FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accountCategory"
|
name="accountCategory"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
|
|
@ -306,13 +269,13 @@ export default function EditAccountForBroadcast() {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/> */}
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Nama</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
|
|
@ -326,14 +289,14 @@ export default function EditAccountForBroadcast() {
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="phoneNumber"
|
name="whatsapp"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Nomor Whatsapp</FormLabel>
|
<FormLabel>Nama</FormLabel>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
placeholder="Masukkan nomor whatsapp"
|
placeholder="Masukkan whatsapp"
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,8 @@ import {
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { close, error, loading, success } from "@/config/swal";
|
|
||||||
import { deleteMediaBlastCampaign } from "@/service/broadcast/broadcast";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -61,47 +58,10 @@ const columns: ColumnDef<any>[] = [
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
cell: ({ row, onDeleteSuccess }: any) => {
|
enableHiding: false,
|
||||||
const MySwal = withReactContent(Swal);
|
cell: ({ row }) => {
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Apakah anda ingin menghapus data?",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#dc3545",
|
|
||||||
confirmButtonText: "Iya",
|
|
||||||
cancelButtonText: "Tidak",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
doDeleteAccount(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
async function doDeleteAccount(id: number) {
|
|
||||||
loading();
|
|
||||||
const response = await deleteMediaBlastCampaign(id);
|
|
||||||
close();
|
|
||||||
|
|
||||||
if (response.error) {
|
|
||||||
error(response.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
console.log("Delete response:", response);
|
|
||||||
|
|
||||||
MySwal.fire({
|
|
||||||
icon: "success",
|
|
||||||
title: "Berhasil!",
|
|
||||||
text: "Data berhasil dihapus.",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
timer: 2000,
|
|
||||||
timerProgressBar: true,
|
|
||||||
});
|
|
||||||
// ✅ panggil callback dari parent
|
|
||||||
onDeleteSuccess?.(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -113,113 +73,28 @@ const columns: ColumnDef<any>[] = [
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
|
href={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
|
||||||
>
|
>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Detail
|
Detail
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link
|
<Link
|
||||||
href={`//admin/broadcast/campaign-list/edit/${row.original.id}`}
|
href={`//admin/broadcast/campaign-list/edit/${row.original.id}`}
|
||||||
>
|
>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
<DropdownMenuItem
|
</DropdownMenuItem>
|
||||||
onClick={() => handleDelete(row.original.id)}
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer"
|
<a>Delete</a>
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
|
||||||
// id: "actions",
|
|
||||||
// accessorKey: "action",
|
|
||||||
// header: "Actions",
|
|
||||||
// enableHiding: false,
|
|
||||||
// cell: ({ row, onDeleteSuccess }: any) => {
|
|
||||||
// const MySwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
// const handleDelete = (id: any) => {
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Apakah anda ingin menghapus data?",
|
|
||||||
// showCancelButton: true,
|
|
||||||
// confirmButtonColor: "#dc3545",
|
|
||||||
// confirmButtonText: "Iya",
|
|
||||||
// cancelButtonText: "Tidak",
|
|
||||||
// }).then((result: any) => {
|
|
||||||
// if (result.isConfirmed) {
|
|
||||||
// doDeleteAccount(id);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// async function doDeleteAccount(id: any) {
|
|
||||||
// loading();
|
|
||||||
// const response = await deleteMediaBlastCampaign(id);
|
|
||||||
// close();
|
|
||||||
|
|
||||||
// if (response.error) {
|
|
||||||
// error(response.message);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// console.log("Delete response:", response);
|
|
||||||
|
|
||||||
// MySwal.fire({
|
|
||||||
// icon: "success",
|
|
||||||
// title: "Berhasil!",
|
|
||||||
// text: "Data berhasil dihapus.",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// timer: 2000,
|
|
||||||
// timerProgressBar: true,
|
|
||||||
// });
|
|
||||||
// // ✅ langsung hapus dari state
|
|
||||||
// onDeleteSuccess?.(id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <DropdownMenu>
|
|
||||||
// <DropdownMenuTrigger asChild>
|
|
||||||
// <Button
|
|
||||||
// size="icon"
|
|
||||||
// className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
// >
|
|
||||||
// <MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
// </Button>
|
|
||||||
// </DropdownMenuTrigger>
|
|
||||||
// <DropdownMenuContent className="p-0" align="end">
|
|
||||||
// <Link
|
|
||||||
// href={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
|
|
||||||
// >
|
|
||||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
// Detail
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </Link>
|
|
||||||
// <Link
|
|
||||||
// href={`//admin/broadcast/campaign-list/edit/${row.original.id}`}
|
|
||||||
// >
|
|
||||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
// Edit
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </Link>
|
|
||||||
// <DropdownMenuItem
|
|
||||||
// onClick={() => handleDelete(row.original.id)}
|
|
||||||
// className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer"
|
|
||||||
// >
|
|
||||||
// Delete
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </DropdownMenuContent>
|
|
||||||
// </DropdownMenu>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default columns;
|
export default columns;
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,6 @@ const CampaignListTable = () => {
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
|
||||||
function handleDeleteSuccess(id: number) {
|
|
||||||
setDataTable((prev) => prev.filter((item) => item.id !== id));
|
|
||||||
}
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -148,16 +143,16 @@ const CampaignListTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
|
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
{/* <Link href="/admin/broadcast/campaign-list/account-list">
|
<Link href="/admin/broadcast/campaign-list/account-list">
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
Daftar Akun
|
Daftar Akun
|
||||||
</Button>
|
</Button>
|
||||||
</Link> */}
|
</Link>
|
||||||
<Link href="/admin/broadcast/campaign-list/create">
|
<Link href="/admin/broadcast/campaign-list/create">
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
<NewCampaignIcon size={23} />
|
<NewCampaignIcon size={23} />
|
||||||
|
|
@ -194,14 +189,7 @@ const CampaignListTable = () => {
|
||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>
|
||||||
{/* {flexRender(cell.column.columnDef.cell, cell.getContext())} */}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
{flexRender(cell.column.columnDef.cell, {
|
|
||||||
...cell.getContext(),
|
|
||||||
onDeleteSuccess: (id: number) => {
|
|
||||||
// setDataTable((prev) => prev.filter((item) => item.id !== id)
|
|
||||||
fetchData()
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export default function CreateCampaign() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
className="space-y-3 bg-white rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -1,406 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
|
||||||
import { Icon } from "@iconify/react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useMediaQuery } from "react-responsive";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import ReactDatePicker from "react-datepicker";
|
|
||||||
import { getOnlyDate } from "@/utils/globals";
|
|
||||||
import AccountListTable from "../../account-list/component/table";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import {
|
|
||||||
getMediaBlastCampaignById,
|
|
||||||
getMediaBlastBroadcastList,
|
|
||||||
} from "@/service/broadcast/broadcast";
|
|
||||||
|
|
||||||
// Types
|
|
||||||
interface CampaignData {
|
|
||||||
id: string;
|
|
||||||
no: number;
|
|
||||||
mediaBlastCampaignId: string;
|
|
||||||
mediaBlastCampaign: {
|
|
||||||
title: string;
|
|
||||||
};
|
|
||||||
subject: string;
|
|
||||||
type: string;
|
|
||||||
status: string;
|
|
||||||
sendDate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PaginatedResponse {
|
|
||||||
content: CampaignData[];
|
|
||||||
totalPages: number;
|
|
||||||
totalElements: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PageProps {
|
|
||||||
params: {
|
|
||||||
id: string;
|
|
||||||
locale: string;
|
|
||||||
};
|
|
||||||
searchParams: {
|
|
||||||
page?: string;
|
|
||||||
size?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BroadcastCampaignDetail({
|
|
||||||
params,
|
|
||||||
searchParams,
|
|
||||||
}: PageProps) {
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const { id, locale } = params;
|
|
||||||
const [getData, setGetData] = useState<CampaignData[]>([]);
|
|
||||||
const [totalPage, setTotalPage] = useState<number>(0);
|
|
||||||
const [totalData, setTotalData] = useState<number>(0);
|
|
||||||
const [activeTab, setActiveTab] = useState<
|
|
||||||
"sent" | "schedule" | "account-list"
|
|
||||||
>("sent");
|
|
||||||
const { page, size } = searchParams;
|
|
||||||
const [calenderState, setCalenderState] = useState<boolean>(false);
|
|
||||||
const [typeFilter, setTypeFilter] = useState<string>("email");
|
|
||||||
const [dateRange, setDateRange] = useState<[Date, Date]>([
|
|
||||||
new Date(),
|
|
||||||
new Date(),
|
|
||||||
]);
|
|
||||||
const [startDate, endDate] = dateRange;
|
|
||||||
const [startDateString, setStartDateString] = useState<string | undefined>();
|
|
||||||
const [endDateString, setEndDateString] = useState<string | undefined>();
|
|
||||||
// Table state
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = useState({});
|
|
||||||
const [pagination, setPagination] = useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: parseInt(size || "10"),
|
|
||||||
});
|
|
||||||
const pages = page ? parseInt(page) - 1 : 0;
|
|
||||||
const currentPage = page ? parseInt(page) : 1;
|
|
||||||
const pageSize = parseInt(size || "10");
|
|
||||||
|
|
||||||
const isFHD = useMediaQuery({
|
|
||||||
minWidth: 1920,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setCurrentPage = (pageNumber: number) => {
|
|
||||||
const params = new URLSearchParams(searchParams);
|
|
||||||
params.set("page", pageNumber.toString());
|
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getListPaginationData() {
|
|
||||||
loading();
|
|
||||||
console.log("Type : ", typeFilter);
|
|
||||||
console.log("Date : ", startDateString, endDateString);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await getMediaBlastBroadcastList(
|
|
||||||
pages,
|
|
||||||
activeTab === "schedule",
|
|
||||||
startDateString || "",
|
|
||||||
endDateString || "",
|
|
||||||
typeFilter,
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
close();
|
|
||||||
if (res?.data?.data) {
|
|
||||||
setupData(res.data.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching data:", error);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getListPaginationData();
|
|
||||||
}, [
|
|
||||||
currentPage,
|
|
||||||
pageSize,
|
|
||||||
activeTab,
|
|
||||||
endDateString,
|
|
||||||
startDateString,
|
|
||||||
typeFilter,
|
|
||||||
]);
|
|
||||||
|
|
||||||
function setupData(rawData: PaginatedResponse) {
|
|
||||||
console.log("raw", rawData);
|
|
||||||
if (rawData !== undefined) {
|
|
||||||
const dataContent = rawData?.content;
|
|
||||||
const data: CampaignData[] = [];
|
|
||||||
|
|
||||||
dataContent.forEach((element, i) => {
|
|
||||||
element.no = (currentPage - 1) * pageSize + i + 1;
|
|
||||||
data.push(element);
|
|
||||||
});
|
|
||||||
|
|
||||||
setGetData(data);
|
|
||||||
setTotalPage(rawData?.totalPages);
|
|
||||||
setTotalData(rawData?.totalElements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: ColumnDef<CampaignData>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mediaBlastCampaign.title",
|
|
||||||
header: "Campaign",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Link
|
|
||||||
href={`/${locale}/admin/broadcast/campaign-list/detail/${row.original.mediaBlastCampaignId}`}
|
|
||||||
className="text-dark"
|
|
||||||
>
|
|
||||||
<span className="font-weight-bold">
|
|
||||||
{row.original.mediaBlastCampaign?.title}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "subject",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Link
|
|
||||||
href={`/${locale}/admin/broadcast/content/detail/${row.original.id}`}
|
|
||||||
className="text-dark"
|
|
||||||
>
|
|
||||||
<span className="font-weight-bold">{row.getValue("subject")}</span>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "type",
|
|
||||||
header: "Tipe",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-right text-black">{row.getValue("type")}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "status",
|
|
||||||
header: "Status",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-right text-black">{row.getValue("status")}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "sendDate",
|
|
||||||
header: "Tanggal & Waktu",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-black">{row.getValue("sendDate")}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: getData,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function initState() {
|
|
||||||
if (startDate != null && endDate != null) {
|
|
||||||
setStartDateString(getOnlyDate(startDate));
|
|
||||||
setEndDateString(getOnlyDate(endDate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("date range", dateRange);
|
|
||||||
initState();
|
|
||||||
}, [calenderState, startDate, endDate]);
|
|
||||||
|
|
||||||
const handleTypeFilter = (type: string) => {
|
|
||||||
setTypeFilter(type);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-white dark:bg-black container-fluid rounded ">
|
|
||||||
<div className="mt-1 p-4">
|
|
||||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-4">
|
|
||||||
<Button
|
|
||||||
onClick={() => setActiveTab("sent")}
|
|
||||||
size="md"
|
|
||||||
className={`hover:text-white ${
|
|
||||||
activeTab === "sent"
|
|
||||||
? "bg-indigo-600 text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Sent
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => setActiveTab("schedule")}
|
|
||||||
size="md"
|
|
||||||
className={`hover:text-white ${
|
|
||||||
activeTab === "schedule"
|
|
||||||
? "bg-indigo-600 text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Schedule
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => setActiveTab("account-list")}
|
|
||||||
size="md"
|
|
||||||
className={`hover:text-white ${
|
|
||||||
activeTab === "account-list"
|
|
||||||
? "bg-indigo-600 text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
List Akun
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{activeTab === "account-list" ? (
|
|
||||||
<AccountListTable />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="broadcast-filter flex flex-column gap-3 mb-4">
|
|
||||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit h-fit">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleTypeFilter("email")}
|
|
||||||
className={`hover:text-white ${
|
|
||||||
typeFilter === "email"
|
|
||||||
? "bg-black text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
Email Blast
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => handleTypeFilter("wa")}
|
|
||||||
className={`hover:text-white ${
|
|
||||||
typeFilter === "wa"
|
|
||||||
? "bg-black text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
WhatsApp Blast
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="dashboard-date-picker">
|
|
||||||
<div className="mx-6 my-1">
|
|
||||||
<ReactDatePicker
|
|
||||||
selectsRange
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onChange={(update) => {
|
|
||||||
setDateRange(update as [Date, Date]);
|
|
||||||
}}
|
|
||||||
placeholderText="Pilih Tanggal"
|
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
|
||||||
className="form-control rounded-pill"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full overflow-x-auto">
|
|
||||||
<Table className="overflow-hidden mt-3">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className="h-[75px]"
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext()
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={columns.length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -130,7 +130,7 @@ export default function EditCampaign() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
className="space-y-3 bg-white rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import DetailContentBlast from "@/components/form/broadcast/content-blast--detail-form";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
|
|
||||||
export default function DetailEmailBlast() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<DetailContentBlast />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "fileTypeName",
|
|
||||||
header: "Konten",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("fileTypeName")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "categoryName",
|
|
||||||
header: "Kategori",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "createdAt",
|
|
||||||
header: "Tanggal Unggah",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "statusName",
|
|
||||||
header: "Status",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("statusName")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Actions",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link href={`/contributor/content/image/detail/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Detail
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<Link href={`/admin/broadcast/create/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Email & Whatsapp Blast
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
{/* <Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Whatsapp Blast
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link> */}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -5,7 +5,7 @@ export default function CreateEmailBlast() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<ContentBlast />
|
<ContentBlast type="email" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -31,9 +31,6 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
import { setBanner } from "@/service/settings/settings";
|
|
||||||
import { error } from "@/config/swal";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -47,6 +44,11 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Judul",
|
header: "Judul",
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fileTypeName",
|
||||||
|
header: "Konten",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("fileTypeName")}</span>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "categoryName",
|
accessorKey: "categoryName",
|
||||||
header: "Kategori",
|
header: "Kategori",
|
||||||
|
|
@ -72,14 +74,6 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const handleBanner = async (id: number) => {
|
|
||||||
const response = setBanner(id, true);
|
|
||||||
toast({
|
|
||||||
title: "Success",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -99,9 +93,14 @@ const columns: ColumnDef<any>[] = [
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<a onClick={() => handleBanner(row.original.id)}>
|
<Link href={`/admin/broadcast/email/${row.original.id}`}>
|
||||||
Jadikan Banner
|
Email Blast
|
||||||
</a>
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
||||||
|
Whatsapp Blast
|
||||||
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
@ -204,7 +204,7 @@ const BroadcastEmailTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between ">
|
<div className="flex justify-between ">
|
||||||
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import BroadcastTable from "./create/component/table";
|
import BroadcastTable from "./email/component/table";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
|
|
||||||
import EscalationTable from "../../shared/communication/escalation/components/escalation-table";
|
import EscalationTable from "../../shared/communication/escalation/components/escalation-table";
|
||||||
|
|
@ -8,7 +8,7 @@ import InternalTable from "../../shared/communication/internal/components/intern
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import BroadcastEmailTable from "./create/component/table";
|
import BroadcastEmailTable from "./email/component/table";
|
||||||
import BroadcastWhatsAppTable from "./whatsapp/component/table";
|
import BroadcastWhatsAppTable from "./whatsapp/component/table";
|
||||||
|
|
||||||
export default function AdminBroadcast() {
|
export default function AdminBroadcast() {
|
||||||
|
|
@ -16,7 +16,33 @@ export default function AdminBroadcast() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setTab("Email Blast")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
tab === "Email Blast"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Email Blast
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setTab("WhatsApp Blast")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
tab === "WhatsApp Blast"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
WhatsApp Blast
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
{tab === "Email Blast" && <BroadcastEmailTable />}
|
{tab === "Email Blast" && <BroadcastEmailTable />}
|
||||||
{tab === "WhatsApp Blast" && <BroadcastWhatsAppTable />}
|
{tab === "WhatsApp Blast" && <BroadcastWhatsAppTable />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export default function CreateWABlast() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
{/* <ContentBlast /> */}
|
<ContentBlast type="wa" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,21 +80,23 @@ const columns: ColumnDef<any>[] = [
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<Link href={`/contributor/content/image/detail/${row.original.id}`}>
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
<Link
|
||||||
|
href={`/contributor/content/image/detail/${row.original.id}`}
|
||||||
|
>
|
||||||
Detail
|
Detail
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link href={`/admin/broadcast/email/${row.original.id}`}>
|
<Link href={`/admin/broadcast/email/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Email Blast
|
Email Blast
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
|
||||||
Whatsapp Blast
|
Whatsapp Blast
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ export default function EditUserForm() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const passwordVal = form.watch("password");
|
||||||
|
const confPasswordVal = form.watch("confirmPassword");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initData();
|
initData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,6 @@ export default function EditUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
isAdmin: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loading();
|
loading();
|
||||||
|
|
@ -353,8 +352,8 @@ export default function EditUserForm() {
|
||||||
<PasswordChecklist
|
<PasswordChecklist
|
||||||
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
||||||
minLength={8}
|
minLength={8}
|
||||||
value={passwordVal || ""}
|
value={passwordVal}
|
||||||
valueAgain={confPasswordVal || ""}
|
valueAgain={confPasswordVal}
|
||||||
onChange={(isValid) => {
|
onChange={(isValid) => {
|
||||||
form.setValue("isValidPassword", isValid);
|
form.setValue("isValidPassword", isValid);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Check, ChevronsUpDown, Eye, EyeOff } from "lucide-react";
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
|
@ -173,8 +174,7 @@ export default function CreateUserForm() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const levelName = getCookiesDecrypt("ulnae");
|
const levelName = getCookiesDecrypt("ulnae");
|
||||||
const [roleList, setRoleList] = useState<RoleData[]>([]);
|
const [roleList, setRoleList] = useState<RoleData[]>([]);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
||||||
const [userEducations, setUserEducations] = useState<any>();
|
const [userEducations, setUserEducations] = useState<any>();
|
||||||
const [userSchools, setUserSchools] = useState<any>();
|
const [userSchools, setUserSchools] = useState<any>();
|
||||||
const [userCompetencies, setUserCompetencies] = useState<any>();
|
const [userCompetencies, setUserCompetencies] = useState<any>();
|
||||||
|
|
@ -285,21 +285,10 @@ export default function CreateUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
isAdmin: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
if (data.role == "OPT-ID") {
|
||||||
// req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
||||||
if (data.role == "OPT-ID") {
|
|
||||||
let snsValue = data?.sns ? data.sns.join(",") : "";
|
|
||||||
|
|
||||||
// ✅ Jika hanya 1 value → tambahkan koma agar backend tidak error
|
|
||||||
if (data?.sns && data.sns.length === 1) {
|
|
||||||
snsValue = snsValue + ",";
|
|
||||||
}
|
|
||||||
|
|
||||||
req.handledSocialMedia = snsValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.role == "KUR-ID") {
|
if (data.role == "KUR-ID") {
|
||||||
|
|
@ -352,7 +341,7 @@ export default function CreateUserForm() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-6 bg-white dark:bg-black p-10 w-full"
|
className="space-y-6 bg-white p-10 w-full"
|
||||||
>
|
>
|
||||||
<p className="text-xl">Data Pengelola Media Hub</p>
|
<p className="text-xl">Data Pengelola Media Hub</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
@ -704,26 +693,18 @@ export default function CreateUserForm() {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative w-1/2">
|
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? "text" : "password"}
|
type="password"
|
||||||
placeholder="Masukkan kata sandi"
|
placeholder="Masukkan kata sandi"
|
||||||
{...field}
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
|
|
@ -731,37 +712,23 @@ export default function CreateUserForm() {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Konfirmasi Kata Sandi</FormLabel>
|
<FormLabel>Konfirmasi Kata Sandi</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative w-1/2">
|
|
||||||
<Input
|
<Input
|
||||||
type={showConfirmPassword ? "text" : "password"}
|
type="password"
|
||||||
placeholder="Masukkan kata sandi"
|
placeholder="Masukkan kata sandi"
|
||||||
{...field}
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setShowConfirmPassword(!showConfirmPassword)
|
|
||||||
}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500"
|
|
||||||
>
|
|
||||||
{showConfirmPassword ? (
|
|
||||||
<EyeOff size={18} />
|
|
||||||
) : (
|
|
||||||
<Eye size={18} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PasswordChecklist
|
<PasswordChecklist
|
||||||
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
||||||
minLength={8}
|
minLength={8}
|
||||||
value={passwordVal || ""}
|
value={passwordVal}
|
||||||
valueAgain={confPasswordVal || ""}
|
valueAgain={confPasswordVal}
|
||||||
onChange={(isValid) => {
|
onChange={(isValid) => {
|
||||||
form.setValue("isValidPassword", isValid);
|
form.setValue("isValidPassword", isValid);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,8 @@ export default function DetailUserForm() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const passwordVal = form.watch("password");
|
||||||
|
const confPasswordVal = form.watch("confirmPassword");
|
||||||
const selectedRole = form.watch("role");
|
const selectedRole = form.watch("role");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ export default function EditUserForm() {
|
||||||
form.setValue("level", String(res?.userLevelId));
|
form.setValue("level", String(res?.userLevelId));
|
||||||
} else {
|
} else {
|
||||||
initFetch();
|
initFetch();
|
||||||
|
console.log("sadad", res?.role?.code);
|
||||||
form.setValue("fullname", res?.fullname);
|
form.setValue("fullname", res?.fullname);
|
||||||
form.setValue("username", res?.username);
|
form.setValue("username", res?.username);
|
||||||
form.setValue("phoneNumber", res?.phoneNumber);
|
form.setValue("phoneNumber", res?.phoneNumber);
|
||||||
|
|
@ -301,7 +302,7 @@ export default function EditUserForm() {
|
||||||
|
|
||||||
async function save(data: z.infer<typeof FormSchema>) {
|
async function save(data: z.infer<typeof FormSchema>) {
|
||||||
let req: any = {
|
let req: any = {
|
||||||
id: Number(id),
|
id: id,
|
||||||
firstName: data.fullname,
|
firstName: data.fullname,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
roleId: data.role,
|
roleId: data.role,
|
||||||
|
|
@ -312,7 +313,6 @@ export default function EditUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
isAdmin: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
if (data.role == "OPT-ID") {
|
||||||
|
|
@ -755,8 +755,8 @@ export default function EditUserForm() {
|
||||||
<PasswordChecklist
|
<PasswordChecklist
|
||||||
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
||||||
minLength={8}
|
minLength={8}
|
||||||
value={passwordVal || ""}
|
value={passwordVal}
|
||||||
valueAgain={confPasswordVal || ""}
|
valueAgain={confPasswordVal}
|
||||||
onChange={(isValid) => {
|
onChange={(isValid) => {
|
||||||
form.setValue("isValidPassword", isValid);
|
form.setValue("isValidPassword", isValid);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default function ManagementUser() {
|
||||||
<ManagementUserVisualization />
|
<ManagementUserVisualization />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5">
|
<section className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
Data User {isInternal ? "Internal" : "Eksternal"}
|
Data User {isInternal ? "Internal" : "Eksternal"}
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "date",
|
|
||||||
header: "Tanggal",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span>
|
|
||||||
{row.original.startDate.split(" ")[0]} -{" "}
|
|
||||||
{row.original.endDate.split(" ")[0]}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul Berita",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "pageUrl",
|
|
||||||
header: "Link Berita",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<a
|
|
||||||
className="cursor-pointer normal-case"
|
|
||||||
href={row.original.mediaUpload.pageUrl}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{row.original.mediaUpload.pageUrl}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Aksi",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link href={`/admin/media-tracking/detail/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
"use client";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { close, error, loading, successCallback } from "@/config/swal";
|
|
||||||
import { getMediaTracking, listDataAllNonPagination, listDataTracking, mediaTrackingSave } from "@/service/media-tracking/media-tracking";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function TrackingMediaModal(props: { triggerFetch: () => void }) {
|
|
||||||
const [content, setContent] = useState<any>([]);
|
|
||||||
const [inputValue, setInputValue] = useState("");
|
|
||||||
const [selectedId, setSelectedId] = useState(0);
|
|
||||||
const [search, setSearch] = useState<string>("");
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [showData, setShowData] = useState("10");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initFecth();
|
|
||||||
}, [page, showData, search]);
|
|
||||||
|
|
||||||
const initFecth = async () => {
|
|
||||||
const response = await getMediaTracking(page - 1, search, showData);
|
|
||||||
setContent(response?.data?.data.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fecthAll = async (search?: string) => {
|
|
||||||
const response = await listDataAllNonPagination(search || inputValue);
|
|
||||||
setContent(response?.data?.data.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
const nowLength = value.split(" ").length;
|
|
||||||
const prevLength = inputValue.split(" ").length;
|
|
||||||
|
|
||||||
setInputValue(value);
|
|
||||||
|
|
||||||
if (value === "") {
|
|
||||||
initFecth();
|
|
||||||
setSelectedId(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/\s/.test(value)) {
|
|
||||||
console.log("Terdapat spasi dalam input");
|
|
||||||
fecthAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nowLength !== prevLength) {
|
|
||||||
fecthAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const doSave = async () => {
|
|
||||||
loading();
|
|
||||||
const req = {
|
|
||||||
mediaUploadId: selectedId,
|
|
||||||
duration: 24,
|
|
||||||
scrapingPeriod: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await mediaTrackingSave(req);
|
|
||||||
if (res?.error) {
|
|
||||||
error(res?.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
|
|
||||||
toast("Berhasil Menambahkan", {
|
|
||||||
description: "",
|
|
||||||
});
|
|
||||||
props.triggerFetch();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button className="bg-blue-600" size="md">
|
|
||||||
Tracking Berita
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Form Tracking Berita</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{" "}
|
|
||||||
<Input type="text" placeholder="Content" value={inputValue} onChange={handleInputChange} />{" "}
|
|
||||||
<div className="w-full border rounded-sm text-xs p-2 flex flex-col max-h-[300px] overflow-auto">
|
|
||||||
{content.length > 0 &&
|
|
||||||
content.map((item: any) => (
|
|
||||||
<a
|
|
||||||
key={item.id}
|
|
||||||
className={`cursor-pointer px-2 py-4 ${selectedId === item.id ? "font-bold bg-gray-200 rounded-sm" : "hover:bg-gray-100 hover:font-semibold"}`}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedId(item.id);
|
|
||||||
setInputValue(item.title);
|
|
||||||
fecthAll(item.title);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <DialogFooter className="sm:justify-start">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button type="button">Close</Button>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter> */}
|
|
||||||
<DialogFooter className="flex justify-end gap-2">
|
|
||||||
<Button className="bg-blue-600 text-white" onClick={doSave}>
|
|
||||||
Tracking Berita
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,371 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronDown,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import columns from "./column";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import TrackingMediaModal from "./modal";
|
|
||||||
import { getMediaTracking } from "@/service/media-tracking/media-tracking";
|
|
||||||
|
|
||||||
const NewsTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const asPath = usePathname();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [showTable, setShowTable] = React.useState(false);
|
|
||||||
const [onSearch, setOnSearch] = React.useState("");
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<any>([]);
|
|
||||||
const [searchTitle, setSearchTitle] = React.useState<string>("");
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [imageData, setImageData] = React.useState<any>();
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const [contentAll, setContentAll] = React.useState([]);
|
|
||||||
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
|
||||||
const [totalContent, setTotalContent] = React.useState();
|
|
||||||
const [search, setSearch] = React.useState<string>("");
|
|
||||||
const group = searchParams?.get("group");
|
|
||||||
const title = searchParams?.get("title");
|
|
||||||
const categorie = searchParams?.get("category");
|
|
||||||
const table = useReactTable({
|
|
||||||
data: dataTable,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let typingTimer: any;
|
|
||||||
const doneTypingInterval = 1500;
|
|
||||||
|
|
||||||
// async function doneTyping() {
|
|
||||||
// fetchData();
|
|
||||||
// }
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getDataTable();
|
|
||||||
}, [page, showData, search]);
|
|
||||||
|
|
||||||
const getDataTable = async () => {
|
|
||||||
const res = await getMediaTracking(page - 1, search, showData);
|
|
||||||
|
|
||||||
const data = res?.data?.data;
|
|
||||||
const newData = data?.content;
|
|
||||||
|
|
||||||
newData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
setDataTable(newData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
setTotalContent(data.totalElements);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getCategories() {
|
|
||||||
const category = await listEnableCategory("");
|
|
||||||
const resCategory = category?.data?.data?.content;
|
|
||||||
setCategories(resCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (categorie) {
|
|
||||||
setCategoryFilter(
|
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"Kategori",
|
|
||||||
categorie,
|
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [categorie]);
|
|
||||||
|
|
||||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
|
||||||
let filter = [...categoryFilter];
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
filter = [...categoryFilter, String(id)];
|
|
||||||
} else {
|
|
||||||
filter.splice(categoryFilter.indexOf(id), 1);
|
|
||||||
}
|
|
||||||
console.log("checkbox filter", filter);
|
|
||||||
setCategoryFilter(filter);
|
|
||||||
router.push(`?category=${filter.join("&")}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanCheckbox = () => {
|
|
||||||
setCategoryFilter([]);
|
|
||||||
setFormatFilter([]);
|
|
||||||
router.push(`?category=&title=`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// async function getData() {
|
|
||||||
// if (asPath?.includes("/polda/") == true) {
|
|
||||||
// if (asPath?.split("/")[2] !== "[polda_name]") {
|
|
||||||
// const filter =
|
|
||||||
// categoryFilter?.length > 0
|
|
||||||
// ? categoryFilter?.sort().join(",")
|
|
||||||
// : categorie || "";
|
|
||||||
|
|
||||||
// const name = title == undefined ? "" : title;
|
|
||||||
// const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
|
||||||
// const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
|
||||||
// loading();
|
|
||||||
// const response = await listDataAll("", name, filter, "");
|
|
||||||
// close();
|
|
||||||
// // setGetTotalPage(response?.data?.data?.totalPages);
|
|
||||||
// // setContentImage(response?.data?.data?.content);
|
|
||||||
// // setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
// const data = response?.data?.data;
|
|
||||||
// const contentData = data?.content;
|
|
||||||
// setImageData(contentData);
|
|
||||||
// setTotalData(data?.totalElements);
|
|
||||||
// setContentAll(response?.data?.data?.content);
|
|
||||||
// setTotalPage(data?.totalPages);
|
|
||||||
// setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setSearch(e.target.value);
|
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function doneTyping() {
|
|
||||||
if (searchTitle == "" || searchTitle == undefined) {
|
|
||||||
router.push("?title=");
|
|
||||||
} else {
|
|
||||||
router.push(`?title=${searchTitle}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border ">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<p className="text-xl font-medium text-default-900">
|
|
||||||
Tracking Berita hari ini!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between sm:items-center md:items-center lg:items-center px-1">
|
|
||||||
{/* <TrackingMediaModal triggerFetch={() => getDataTable()} /> */}
|
|
||||||
<div className=" flex flex-row items-center gap-3">
|
|
||||||
<div className="flex items-center py-2">
|
|
||||||
<div className="mx-3">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
1 - {showData} Data
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 text-sm">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={showData}
|
|
||||||
onValueChange={setShowData}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="10">
|
|
||||||
1 - 10 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="50">
|
|
||||||
1 - 50 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="100">
|
|
||||||
1 - 100 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="250">
|
|
||||||
1 - 250 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center py-4">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" className="ml-auto" size="md">
|
|
||||||
Columns <ChevronDown />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
{table
|
|
||||||
.getAllColumns()
|
|
||||||
.filter((column) => column.getCanHide())
|
|
||||||
.map((column) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={column.id}
|
|
||||||
className="capitalize"
|
|
||||||
checked={column.getIsVisible()}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
column.toggleVisibility(!!value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{column.id}
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table className="overflow-hidden">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow key={row.id} className="h-[75px]">
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
{/* <div className="flex justify-end mt-4">
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
className="text-sm mr-3"
|
|
||||||
onClick={() => setShowTable(false)}
|
|
||||||
>
|
|
||||||
Tracking Berita Baru
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
{/* </>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NewsTable;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import NewsTable from "../../component/table";
|
|
||||||
import NewsDetailTable from "../component/table";
|
|
||||||
|
|
||||||
export default function DetailNews() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<NewsDetailTable />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
import { validateMediaLink } from "@/service/media-tracking/media-tracking";
|
|
||||||
import toast from "react-hot-toast";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "source",
|
|
||||||
header: "Media Online",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className="normal-case">{row.getValue("source")}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul Berita",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className="normal-case">{row.getValue("title")}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// accessorKey: "link",
|
|
||||||
// header: "Link Berita",
|
|
||||||
// cell: ({ row }) => (
|
|
||||||
// <span className="normal-case">{row.getValue("link")}</span>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
accessorKey: "link",
|
|
||||||
header: "Link Berita",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const link = row.getValue<string>("link");
|
|
||||||
|
|
||||||
if (!link) {
|
|
||||||
return <span className="text-muted-foreground">-</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
href={link}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 underline hover:text-blue-800 break-all"
|
|
||||||
>
|
|
||||||
{link}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "validation",
|
|
||||||
header: "Validasi",
|
|
||||||
cell: ({ row, table }) => {
|
|
||||||
const original = row.original;
|
|
||||||
|
|
||||||
// const isValid = original.isValid;
|
|
||||||
const isRelevant = original.isRelevant;
|
|
||||||
const link = original.link;
|
|
||||||
|
|
||||||
const updateRow = (data: Partial<any>) => {
|
|
||||||
table.options.meta?.updateData(row.index, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleValid = async () => {
|
|
||||||
try {
|
|
||||||
await validateMediaLink(original.id, true);
|
|
||||||
updateRow({
|
|
||||||
isRelevant: true,
|
|
||||||
});
|
|
||||||
table.options.meta?.refetchData?.();
|
|
||||||
} catch (err: any) {
|
|
||||||
toast.error(err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInvalid = async () => {
|
|
||||||
try {
|
|
||||||
await validateMediaLink(original.id, false);
|
|
||||||
|
|
||||||
updateRow({
|
|
||||||
isRelevant: false,
|
|
||||||
});
|
|
||||||
table.options.meta?.refetchData?.();
|
|
||||||
} catch (err: any) {
|
|
||||||
toast.error(err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!link) {
|
|
||||||
return <span className="text-muted-foreground">-</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRelevant === true) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="bg-green-600 hover:bg-green-700"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
Relevan
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleValid}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M18.7 7.2c-.4-.4-1-.4-1.4 0l-7.5 7.5l-3.1-3.1c-.4-.4-1-.4-1.4 0s-.4 1 0 1.4l3.8 3.8c.2.2.4.3.7.3s.5-.1.7-.3l8.2-8.2c.4-.4.4-1 0-1.4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Relevan
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={handleInvalid} className="flex text-center items-center justify-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="1.5"
|
|
||||||
d="M6.758 17.243L12.001 12m5.243-5.243L12 12m0 0L6.758 6.757M12.001 12l5.243 5.243"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Tidak Relevan
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,272 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
|
||||||
import { paginationBlog } from "@/service/blog/blog";
|
|
||||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import columns from "./column";
|
|
||||||
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { listDataMedia } from "@/service/broadcast/broadcast";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link } from "@/i18n/routing";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { link } from "fs";
|
|
||||||
import { getMediaTrackingResult } from "@/service/media-tracking/media-tracking";
|
|
||||||
|
|
||||||
const NewsDetailTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const param = useParams();
|
|
||||||
const id = param?.id;
|
|
||||||
const [search, setSearch] = React.useState("");
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [showTable, setShowTable] = React.useState(false);
|
|
||||||
const [link, setLink] = React.useState("");
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const table = useReactTable({
|
|
||||||
data: dataTable,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
meta: {
|
|
||||||
updateData: (rowIndex: number, value: Partial<any>) => {
|
|
||||||
setDataTable((old) =>
|
|
||||||
old.map((row, index) =>
|
|
||||||
index === rowIndex ? { ...row, ...value } : row
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
refetchData: () => {
|
|
||||||
fetchData();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let typingTimer: any;
|
|
||||||
const doneTypingInterval = 1500;
|
|
||||||
|
|
||||||
// const handleKeyUp = () => {
|
|
||||||
// clearTimeout(typingTimer);
|
|
||||||
// typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
};
|
|
||||||
|
|
||||||
// async function doneTyping() {
|
|
||||||
// fetchData();
|
|
||||||
// }
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
setPagination({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
}, [page, showData, id]);
|
|
||||||
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
const res = await getMediaTrackingResult({ id: id, page: page - 1 });
|
|
||||||
const data = res?.data?.data;
|
|
||||||
const contentData = data?.content;
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("contentData : ", data);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
close();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching tasks:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function getCategories() {
|
|
||||||
const category = await listEnableCategory("");
|
|
||||||
const resCategory = category?.data?.data?.content;
|
|
||||||
setCategories(resCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (type: string, id: number, checked: boolean) => {
|
|
||||||
if (type === "category") {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...categoryFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = categoryFilter.filter((a) => a !== id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...statusFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = statusFilter.filter((a) => a !== id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border">
|
|
||||||
<Table className="overflow-hidden mt-4">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow key={row.id} className="h-[75px]">
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NewsDetailTable;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import NewsTable from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminNews() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<NewsTable />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { exportMediaTrackingToExcel } from "@/utils/export-media-tracking";
|
|
||||||
import { loading, close } from "@/config/swal";
|
|
||||||
import { error } from "@/lib/swal";
|
|
||||||
import {
|
|
||||||
DownloadIcon,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "resultTotal",
|
|
||||||
header: () => <div className="text-center w-full">Total Artikel</div>,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const value = row.getValue("resultTotal") as number | string | null;
|
|
||||||
|
|
||||||
const finalValue =
|
|
||||||
value === null || value === undefined || value === ""
|
|
||||||
? 0
|
|
||||||
: Number(value);
|
|
||||||
|
|
||||||
return <div className="text-center w-full">{finalValue}</div>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "amplification",
|
|
||||||
header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const raw = row.getValue("amplification") as string | null;
|
|
||||||
|
|
||||||
let total = 0;
|
|
||||||
let invalidTotal = 0;
|
|
||||||
|
|
||||||
if (raw && typeof raw === "string") {
|
|
||||||
const parts = raw.split("/").map((v) => v.trim());
|
|
||||||
total = Number(parts[0]) || 0;
|
|
||||||
invalidTotal = Number(parts[1]) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-center w-full font-medium">
|
|
||||||
{total}
|
|
||||||
<span className="text-muted-foreground">/{invalidTotal}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// {
|
|
||||||
// accessorKey: "status",
|
|
||||||
// header: "Status",
|
|
||||||
// cell: ({ row }) => <span>{row.getValue("status")}</span>,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// accessorKey: "isProcessing",
|
|
||||||
// header: () => <div className="text-center">Status</div>,
|
|
||||||
// cell: ({ row }) => {
|
|
||||||
// const raw = row.getValue("isProcessing");
|
|
||||||
// var status = "Sedang Diproses"
|
|
||||||
// if (Boolean(raw) == true) {
|
|
||||||
// status = "Selesai Diproses";
|
|
||||||
// }
|
|
||||||
// return <div className="text-center">{status}</div>;
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
accessorKey: "isProcessing",
|
|
||||||
header: () => <div className="text-center">Status</div>,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const raw = Boolean(row.getValue("isProcessing"));
|
|
||||||
const statusText = raw ? "Sedang Diproses" : "Sudah Selesai";
|
|
||||||
const colorClass = raw
|
|
||||||
? "bg-yellow-100 text-yellow-700 border border-yellow-300"
|
|
||||||
: "bg-green-100 text-green-700 border border-green-300";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-center">
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 rounded text-xs font-medium inline-block ${colorClass}`}
|
|
||||||
>
|
|
||||||
{statusText}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "createdAt",
|
|
||||||
header: () => <div className="text-center">Tanggal Penarikan</div>,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const raw = row.getValue("createdAt");
|
|
||||||
if (!raw || typeof raw !== "string")
|
|
||||||
return <div className="text-center">-</div>;
|
|
||||||
|
|
||||||
const date = new Date(raw);
|
|
||||||
if (isNaN(date.getTime())) return <div className="text-center">-</div>;
|
|
||||||
|
|
||||||
const formatted = date.toLocaleDateString("id-ID", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "short",
|
|
||||||
year: "numeric",
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div className="text-center">{formatted}</div>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Action",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link href={`/admin/media-tracking/detail/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b cursor-pointer text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
{row.original.mediaUpload.fileType.secondaryName &&
|
|
||||||
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="p-2 border-b cursor-pointer text-default-700 group rounded-none focus:bg-default focus:text-primary-foreground "
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
await exportMediaTrackingToExcel({
|
|
||||||
mediaTrackingId: row.original.id,
|
|
||||||
});
|
|
||||||
close();
|
|
||||||
} catch (e: any) {
|
|
||||||
close();
|
|
||||||
error(e.message || "Gagal export data");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DownloadIcon className="w-4 h-4 me-1.5" />
|
|
||||||
<p>Download</p>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,382 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronDown,
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import columns from "./column";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { link } from "fs";
|
|
||||||
import { listDataAll } from "@/service/landing/landing";
|
|
||||||
import SearchImageComponent from "@/components/form/media-tracking/search-image-card";
|
|
||||||
import SearchVideoComponent from "@/components/form/media-tracking/search-video-card";
|
|
||||||
import SearchDocumentComponent from "@/components/form/media-tracking/search-document-card";
|
|
||||||
import SearchAudioComponent from "@/components/form/media-tracking/search-audio-card";
|
|
||||||
import { getMediaTracking } from "@/service/media-tracking/media-tracking";
|
|
||||||
import { group } from "console";
|
|
||||||
import router from "next/router";
|
|
||||||
import { title } from "process";
|
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
|
|
||||||
const ResultTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const asPath = usePathname();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [showTable, setShowTable] = React.useState(false);
|
|
||||||
const [onSearch, setOnSearch] = React.useState("");
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<any>([]);
|
|
||||||
const [searchTitle, setSearchTitle] = React.useState<string>("");
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [imageData, setImageData] = React.useState<any>();
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const [contentAll, setContentAll] = React.useState([]);
|
|
||||||
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
|
||||||
const [totalContent, setTotalContent] = React.useState();
|
|
||||||
const [search, setSearch] = React.useState<string>("");
|
|
||||||
const group = searchParams?.get("group");
|
|
||||||
const title = searchParams?.get("title");
|
|
||||||
const categorie = searchParams?.get("category");
|
|
||||||
const table = useReactTable({
|
|
||||||
data: dataTable,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let typingTimer: any;
|
|
||||||
const doneTypingInterval = 1500;
|
|
||||||
|
|
||||||
// async function doneTyping() {
|
|
||||||
// fetchData();
|
|
||||||
// }
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getDataTable();
|
|
||||||
}, [page, showData, search]);
|
|
||||||
|
|
||||||
const getDataTable = async () => {
|
|
||||||
const res = await getMediaTracking(page - 1, search, showData);
|
|
||||||
|
|
||||||
const data = res?.data?.data;
|
|
||||||
const newData = data?.content;
|
|
||||||
|
|
||||||
newData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
setDataTable(newData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
setTotalContent(data.totalElements);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getCategories() {
|
|
||||||
const category = await listEnableCategory("");
|
|
||||||
const resCategory = category?.data?.data?.content;
|
|
||||||
setCategories(resCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (categorie) {
|
|
||||||
setCategoryFilter(
|
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"Kategori",
|
|
||||||
categorie,
|
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [categorie]);
|
|
||||||
|
|
||||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
|
||||||
let filter = [...categoryFilter];
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
filter = [...categoryFilter, String(id)];
|
|
||||||
} else {
|
|
||||||
filter.splice(categoryFilter.indexOf(id), 1);
|
|
||||||
}
|
|
||||||
console.log("checkbox filter", filter);
|
|
||||||
setCategoryFilter(filter);
|
|
||||||
router.push(`?category=${filter.join("&")}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanCheckbox = () => {
|
|
||||||
setCategoryFilter([]);
|
|
||||||
setFormatFilter([]);
|
|
||||||
router.push(`?category=&title=`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// async function getData() {
|
|
||||||
// if (asPath?.includes("/polda/") == true) {
|
|
||||||
// if (asPath?.split("/")[2] !== "[polda_name]") {
|
|
||||||
// const filter =
|
|
||||||
// categoryFilter?.length > 0
|
|
||||||
// ? categoryFilter?.sort().join(",")
|
|
||||||
// : categorie || "";
|
|
||||||
|
|
||||||
// const name = title == undefined ? "" : title;
|
|
||||||
// const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
|
||||||
// const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
|
||||||
// loading();
|
|
||||||
// const response = await listDataAll("", name, filter, "");
|
|
||||||
// close();
|
|
||||||
// // setGetTotalPage(response?.data?.data?.totalPages);
|
|
||||||
// // setContentImage(response?.data?.data?.content);
|
|
||||||
// // setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
// const data = response?.data?.data;
|
|
||||||
// const contentData = data?.content;
|
|
||||||
// setImageData(contentData);
|
|
||||||
// setTotalData(data?.totalElements);
|
|
||||||
// setContentAll(response?.data?.data?.content);
|
|
||||||
// setTotalPage(data?.totalPages);
|
|
||||||
// setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setSearch(e.target.value);
|
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function doneTyping() {
|
|
||||||
if (searchTitle == "" || searchTitle == undefined) {
|
|
||||||
router.push("?title=");
|
|
||||||
} else {
|
|
||||||
router.push(`?title=${searchTitle}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3 border ">
|
|
||||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-end sm:items-center md:items-center lg:items-center">
|
|
||||||
<div className=" flex flex-row justify-end items-center gap-3">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="mx-3">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
1 - {showData} Data
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 text-sm">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={showData}
|
|
||||||
onValueChange={setShowData}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="10">
|
|
||||||
1 - 10 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="50">
|
|
||||||
1 - 50 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="100">
|
|
||||||
1 - 100 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="250">
|
|
||||||
1 - 250 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center py-2">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" className="ml-auto" size="md">
|
|
||||||
Columns <ChevronDown />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
{table
|
|
||||||
.getAllColumns()
|
|
||||||
.filter((column) => column.getCanHide())
|
|
||||||
.map((column) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={column.id}
|
|
||||||
className="capitalize"
|
|
||||||
checked={column.getIsVisible()}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
column.toggleVisibility(!!value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{column.id}
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table className="overflow-hidden">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow key={row.id} className="h-[75px]">
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
{/* <div className="flex justify-end mt-4">
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
className="text-sm mr-3"
|
|
||||||
onClick={() => setShowTable(false)}
|
|
||||||
>
|
|
||||||
Tracking Berita Baru
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
{/* </>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ResultTable;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import ResultTable from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminResult() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<ResultTable />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -46,11 +46,25 @@ import {
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { paginationBlog } from "@/service/blog/blog";
|
||||||
|
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./column";
|
import columns from "./column";
|
||||||
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { listDataMedia } from "@/service/broadcast/broadcast";
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -61,26 +75,21 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { link } from "fs";
|
import { link } from "fs";
|
||||||
import { listDataAll } from "@/service/landing/landing";
|
|
||||||
import SearchImageComponent from "@/components/form/media-tracking/search-image-card";
|
|
||||||
import SearchVideoComponent from "@/components/form/media-tracking/search-video-card";
|
|
||||||
import SearchDocumentComponent from "@/components/form/media-tracking/search-document-card";
|
|
||||||
import SearchAudioComponent from "@/components/form/media-tracking/search-audio-card";
|
|
||||||
|
|
||||||
const NewsTable = () => {
|
const NewsTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const asPath = usePathname();
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [search, setSearch] = React.useState("");
|
const [search, setSearch] = React.useState("");
|
||||||
const [showData, setShowData] = React.useState("10");
|
const [showData, setShowData] = React.useState("10");
|
||||||
const [categories, setCategories] = React.useState<any>();
|
const [categories, setCategories] = React.useState<any>();
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
const [showTable, setShowTable] = React.useState(false);
|
const [showTable, setShowTable] = React.useState(false);
|
||||||
const [onSearch, setOnSearch] = React.useState("");
|
const [link, setLink] = React.useState("");
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
|
@ -88,19 +97,10 @@ const NewsTable = () => {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: Number(showData),
|
pageSize: Number(showData),
|
||||||
});
|
});
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<any>([]);
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
const [searchTitle, setSearchTitle] = React.useState<string>("");
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [imageData, setImageData] = React.useState<any>();
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState<number>(1);
|
|
||||||
const [contentAll, setContentAll] = React.useState([]);
|
|
||||||
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
|
||||||
const [totalContent, setTotalContent] = React.useState();
|
|
||||||
const group = searchParams?.get("group");
|
|
||||||
const title = searchParams?.get("title");
|
|
||||||
const categorie = searchParams?.get("category");
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -125,6 +125,15 @@ const NewsTable = () => {
|
||||||
let typingTimer: any;
|
let typingTimer: any;
|
||||||
const doneTypingInterval = 1500;
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
// const handleKeyUp = () => {
|
||||||
|
// clearTimeout(typingTimer);
|
||||||
|
// typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
// async function doneTyping() {
|
// async function doneTyping() {
|
||||||
// fetchData();
|
// fetchData();
|
||||||
// }
|
// }
|
||||||
|
|
@ -136,6 +145,41 @@ const NewsTable = () => {
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// setPagination({
|
||||||
|
// pageIndex: 0,
|
||||||
|
// pageSize: Number(showData),
|
||||||
|
// });
|
||||||
|
// }, [page, showData]);
|
||||||
|
|
||||||
|
// async function fetchData() {
|
||||||
|
// try {
|
||||||
|
// loading();
|
||||||
|
// const res = await listDataMedia(
|
||||||
|
// page - 1,
|
||||||
|
// showData,
|
||||||
|
// search,
|
||||||
|
// categoryFilter?.sort().join(","),
|
||||||
|
// statusFilter?.sort().join(",")
|
||||||
|
// );
|
||||||
|
// const data = res?.data?.data;
|
||||||
|
// const contentData = data?.content;
|
||||||
|
// contentData.forEach((item: any, index: number) => {
|
||||||
|
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log("contentData : ", data);
|
||||||
|
|
||||||
|
// setDataTable(contentData);
|
||||||
|
// setTotalData(data?.totalElements);
|
||||||
|
// setTotalPage(data?.totalPages);
|
||||||
|
// close();
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching tasks:", error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
getCategories();
|
getCategories();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -146,85 +190,30 @@ const NewsTable = () => {
|
||||||
setCategories(resCategory);
|
setCategories(resCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
const handleChange = (type: string, id: number, checked: boolean) => {
|
||||||
if (categorie) {
|
if (type === "category") {
|
||||||
setCategoryFilter(
|
if (checked) {
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
const temp: number[] = [...categoryFilter];
|
||||||
);
|
temp.push(id);
|
||||||
console.log(
|
setCategoryFilter(temp);
|
||||||
"Kategori",
|
|
||||||
categorie,
|
|
||||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [categorie]);
|
|
||||||
|
|
||||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
|
||||||
let filter = [...categoryFilter];
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
filter = [...categoryFilter, String(id)];
|
|
||||||
} else {
|
} else {
|
||||||
filter.splice(categoryFilter.indexOf(id), 1);
|
const temp = categoryFilter.filter((a) => a !== id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
}
|
}
|
||||||
console.log("checkbox filter", filter);
|
|
||||||
setCategoryFilter(filter);
|
|
||||||
router.push(`?category=${filter.join("&")}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanCheckbox = () => {
|
|
||||||
setCategoryFilter([]);
|
|
||||||
setFormatFilter([]);
|
|
||||||
router.push(`?category=&title=`);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getData() {
|
|
||||||
if (asPath?.includes("/polda/") == true) {
|
|
||||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
|
||||||
const filter =
|
|
||||||
categoryFilter?.length > 0
|
|
||||||
? categoryFilter?.sort().join(",")
|
|
||||||
: categorie || "";
|
|
||||||
|
|
||||||
const name = title == undefined ? "" : title;
|
|
||||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
|
||||||
const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
|
||||||
loading();
|
|
||||||
const response = await listDataAll("", name, filter, "");
|
|
||||||
close();
|
|
||||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
|
||||||
// setContentImage(response?.data?.data?.content);
|
|
||||||
// setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
const data = response?.data?.data;
|
|
||||||
const contentData = data?.content;
|
|
||||||
setImageData(contentData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setContentAll(response?.data?.data?.content);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
setTotalContent(response?.data?.data?.totalElements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function doneTyping() {
|
|
||||||
if (searchTitle == "" || searchTitle == undefined) {
|
|
||||||
router.push("?title=");
|
|
||||||
} else {
|
} else {
|
||||||
router.push(`?title=${searchTitle}`);
|
if (checked) {
|
||||||
|
const temp: number[] = [...statusFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = statusFilter.filter((a) => a !== id);
|
||||||
|
setStatusFilter(temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border ">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<p className="text-xl font-medium text-default-900">
|
<p className="text-xl font-medium text-default-900">
|
||||||
Tracking Berita hari ini!
|
Tracking Berita hari ini!
|
||||||
|
|
@ -236,38 +225,30 @@ const NewsTable = () => {
|
||||||
Tracking Berita
|
Tracking Berita
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="overflow-y-auto h-[500px] min-w-max mx-5">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Form Tracking Berita</DialogTitle>
|
<DialogTitle>Form Tracking Berita</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="grid gap-4 py-4 px-5">
|
<div className="grid gap-4 py-4">
|
||||||
<div className="space-y-2 flex flex-col">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="link" className="text-sm font-medium">
|
<Label htmlFor="link" className="text-sm font-medium">
|
||||||
Masukkan Link <span className="text-red-500">*</span>
|
Masukkan Link <span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<Input
|
||||||
value={searchTitle}
|
id="link"
|
||||||
onChange={(e) => setSearchTitle(e.target.value)}
|
placeholder="Masukkan Link disini!"
|
||||||
onKeyUp={handleKeyUp}
|
value={link}
|
||||||
onKeyDown={handleKeyDown}
|
onChange={(e) => setLink(e.target.value)}
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
className="pl-4 pr-4 py-1 w-full h-10 text-[15px] border focus:outline-none dark:text-white"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
<div className="flex flex-col gap-2 w-full">
|
Sisa Kuota Harian: 5 Kata Kunci
|
||||||
<SearchImageComponent categoryFilter={categoryFilter} />
|
</p>
|
||||||
<SearchVideoComponent categoryFilter={categoryFilter} />
|
|
||||||
<SearchDocumentComponent categoryFilter={categoryFilter} />
|
|
||||||
<SearchAudioComponent categoryFilter={categoryFilter} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-2">
|
<DialogFooter className="flex justify-end gap-2">
|
||||||
<Button onClick={cleanCheckbox} variant="outline">
|
<Button variant="outline">Batal</Button>
|
||||||
Riset Filter
|
|
||||||
</Button>
|
|
||||||
<Button className="bg-blue-600 text-white">Tracking Berita</Button>
|
<Button className="bg-blue-600 text-white">Tracking Berita</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Nama Media Online",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "link",
|
|
||||||
header: "Link",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,358 +0,0 @@
|
||||||
"use client";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Card } from "@/components/ui/card";
|
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
||||||
import {
|
|
||||||
listDataAllNonPagination,
|
|
||||||
listDataTracking,
|
|
||||||
mediaTrackingSave,
|
|
||||||
} from "@/service/media-tracking/media-tracking";
|
|
||||||
import { error } from "@/lib/swal";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { PaginationState } from "@tanstack/react-table";
|
|
||||||
import page from "../page";
|
|
||||||
import CustomPagination from "@/components/table/custom-pagination";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
|
|
||||||
export default function TrackingBeritaCard() {
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [content, setContent] = useState<any[]>([]);
|
|
||||||
const [selectedItems, setSelectedItems] = useState<number[]>([]);
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [totalPage, setTotalPage] = useState(1);
|
|
||||||
const [showData, setShowData] = useState("6");
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initFecth();
|
|
||||||
}, [showData, page]);
|
|
||||||
|
|
||||||
const initFecth = async () => {
|
|
||||||
loading();
|
|
||||||
const response = await listDataTracking(Number(showData), page - 1, search);
|
|
||||||
const data = response?.data?.data;
|
|
||||||
const newData = data?.content;
|
|
||||||
setTotalPage(data?.totalPages || 1);
|
|
||||||
newData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
setContent(response?.data?.data?.content || []);
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
|
|
||||||
const fecthAll = async (keyword: string) => {
|
|
||||||
const response = await listDataAllNonPagination(keyword);
|
|
||||||
setContent(response?.data?.data?.content || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
setSearch(value);
|
|
||||||
|
|
||||||
const response = await listDataTracking(Number(showData), 0, value);
|
|
||||||
setContent(response?.data?.data?.content || []);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
// const value = e.target.value;
|
|
||||||
// setSearch(value);
|
|
||||||
|
|
||||||
// if (value.trim() === "") {
|
|
||||||
// initFecth();
|
|
||||||
// } else {
|
|
||||||
// fecthAll(value);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleSelect = (id: number) => {
|
|
||||||
setSelectedItems((prev) =>
|
|
||||||
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const doSave = async () => {
|
|
||||||
if (selectedItems.length === 0) {
|
|
||||||
MySwal.fire(
|
|
||||||
"Peringatan",
|
|
||||||
"Pilih minimal 1 berita untuk disimpan.",
|
|
||||||
"warning"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
|
|
||||||
const promises = selectedItems.map(async (id) => {
|
|
||||||
const res = await mediaTrackingSave({
|
|
||||||
mediaUploadId: id,
|
|
||||||
duration: 24,
|
|
||||||
scrapingPeriod: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
// cek pesan API
|
|
||||||
if (!res?.data?.success) {
|
|
||||||
throw new Error(
|
|
||||||
res?.data?.message ||
|
|
||||||
"Limit media tracking per hari sudah tercapai. Maksimal 5 tracking per hari."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
close();
|
|
||||||
|
|
||||||
await MySwal.fire({
|
|
||||||
icon: "success",
|
|
||||||
title: "Berhasil!",
|
|
||||||
text: "Tracking berita berhasil ditambahkan.",
|
|
||||||
confirmButtonColor: "#2563eb",
|
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedItems([]);
|
|
||||||
initFecth();
|
|
||||||
} catch (err: any) {
|
|
||||||
close();
|
|
||||||
MySwal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Gagal!",
|
|
||||||
text: err?.message || "Terjadi kesalahan saat menyimpan data.",
|
|
||||||
confirmButtonColor: "#dc2626",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const doSave = async () => {
|
|
||||||
// if (selectedItems.length === 0) {
|
|
||||||
// toast("Pilih minimal 1 berita untuk disimpan.");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const promises = selectedItems.map((id) =>
|
|
||||||
// mediaTrackingSave({
|
|
||||||
// mediaUploadId: id,
|
|
||||||
// duration: 24,
|
|
||||||
// scrapingPeriod: 3,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// await Promise.all(promises);
|
|
||||||
|
|
||||||
// toast("Berhasil Menambahkan", {
|
|
||||||
// description: "",
|
|
||||||
// });
|
|
||||||
// setSelectedItems([]);
|
|
||||||
// initFecth();
|
|
||||||
// } catch (err: any) {
|
|
||||||
// error(err?.message || "Gagal menyimpan data.");
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const doSave = async () => {
|
|
||||||
// if (selectedItems.length === 0) {
|
|
||||||
// MySwal.fire(
|
|
||||||
// "Peringatan",
|
|
||||||
// "Pilih minimal 1 berita untuk disimpan.",
|
|
||||||
// "warning"
|
|
||||||
// );
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// loading();
|
|
||||||
|
|
||||||
// const promises = selectedItems.map((id) =>
|
|
||||||
// mediaTrackingSave({
|
|
||||||
// mediaUploadId: id,
|
|
||||||
// duration: 24,
|
|
||||||
// scrapingPeriod: 3,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// await Promise.all(promises);
|
|
||||||
|
|
||||||
// close();
|
|
||||||
|
|
||||||
// await MySwal.fire({
|
|
||||||
// icon: "success",
|
|
||||||
// title: "Berhasil!",
|
|
||||||
// text: "Tracking berita berhasil ditambahkan.",
|
|
||||||
// confirmButtonColor: "#2563eb",
|
|
||||||
// });
|
|
||||||
|
|
||||||
// setSelectedItems([]);
|
|
||||||
// initFecth();
|
|
||||||
// } catch (err: any) {
|
|
||||||
// close();
|
|
||||||
// MySwal.fire({
|
|
||||||
// icon: "error",
|
|
||||||
// title: "Gagal!",
|
|
||||||
// text: err?.message || "Terjadi kesalahan saat menyimpan data.",
|
|
||||||
// confirmButtonColor: "#dc2626",
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const slugify = (text: string) => {
|
|
||||||
return text
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, "-")
|
|
||||||
.replace(/(^-|-$)+/g, "");
|
|
||||||
};
|
|
||||||
|
|
||||||
const goToDetail = (item: any) => {
|
|
||||||
const type = item.type || "image";
|
|
||||||
const slug = slugify(item.title || "");
|
|
||||||
const url = `/in/${type}/detail/${item.id}-${slug}`;
|
|
||||||
|
|
||||||
window.location.href = url;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<div className="p-4 space-y-4">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<Input
|
|
||||||
placeholder="Pencarian"
|
|
||||||
value={search}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
className="max-w-sm"
|
|
||||||
/>
|
|
||||||
<div className="mx-3">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
1 - {showData} Data
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 text-sm">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={showData}
|
|
||||||
onValueChange={setShowData}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="12">
|
|
||||||
1 - 6 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="12">
|
|
||||||
1 - 12 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="60">
|
|
||||||
1 - 60 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="120">
|
|
||||||
1 - 120 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedItems.length > 0 && (
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div className="text-sm text-blue-600 font-medium">
|
|
||||||
{selectedItems.length} Item Terpilih{" "}
|
|
||||||
<span className="text-black">
|
|
||||||
/ Tracking Berita tersisa {5 - selectedItems.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Button className="bg-blue-600 text-white" onClick={doSave}>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
||||||
{content?.length > 0 &&
|
|
||||||
content.map((item: any) => (
|
|
||||||
<Card
|
|
||||||
key={item.id}
|
|
||||||
className="relative overflow-hidden shadow-sm border rounded-lg"
|
|
||||||
>
|
|
||||||
{/* KLIK GAMBAR = CHECKLIST */}
|
|
||||||
<div
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => handleSelect(item.id)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={item.thumbnailLink}
|
|
||||||
alt={item.title}
|
|
||||||
className="w-full h-[300px] object-cover"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* CHECKBOX */}
|
|
||||||
<div className="absolute top-2 left-2">
|
|
||||||
<div className="w-5 h-5 border-2 border-white bg-white rounded-sm flex items-center justify-center">
|
|
||||||
{selectedItems.includes(item.id) && (
|
|
||||||
<div className="w-3 h-3 bg-blue-600 rounded-sm" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* KLIK JUDUL = DETAIL */}
|
|
||||||
<p
|
|
||||||
className="p-2 text-sm font-medium hover:underline cursor-pointer"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
goToDetail(item);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</p>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
||||||
{content?.length > 1 &&
|
|
||||||
content.map((item: any) => (
|
|
||||||
<Card
|
|
||||||
key={item.id}
|
|
||||||
className="relative overflow-hidden shadow-sm border rounded-lg cursor-pointer"
|
|
||||||
onClick={() => handleSelect(item.id)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={item.thumbnailLink}
|
|
||||||
alt={item.title}
|
|
||||||
className="w-full h-[300px] object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute top-2 left-2">
|
|
||||||
<div className="w-5 h-5 border-2 border-white bg-white rounded-sm flex items-center justify-center">
|
|
||||||
{selectedItems.includes(item.id) && (
|
|
||||||
<div className="w-3 h-3 bg-blue-600 rounded-sm" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="p-2 text-sm font-medium text-gray-800 truncate">
|
|
||||||
{item.title}
|
|
||||||
</p>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div> */}
|
|
||||||
<div className="mt-3">
|
|
||||||
{content && content?.length > 0 ? (
|
|
||||||
<CustomPagination
|
|
||||||
totalPage={totalPage}
|
|
||||||
onPageChange={(data) => setPage(data)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p>No Data</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import TrackingBeritaCard from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminTrackingBerita() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<TrackingBeritaCard />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -58,6 +58,16 @@ const columns: ColumnDef<any>[] = [
|
||||||
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "static",
|
||||||
|
header: "Static Banner",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<StaticToogle
|
||||||
|
id={row.original.id}
|
||||||
|
initChecked={row.original.staticPage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "statusName",
|
accessorKey: "statusName",
|
||||||
header: "Status Banner",
|
header: "Status Banner",
|
||||||
|
|
@ -65,6 +75,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} />
|
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,22 @@ const BannerListTable = () => {
|
||||||
let temp: any;
|
let temp: any;
|
||||||
|
|
||||||
const response = await listBanner();
|
const response = await listBanner();
|
||||||
const data = response?.data?.data?.content;
|
temp = response?.data?.data;
|
||||||
console.log("banner", data);
|
const response2 = await listStaticBanner();
|
||||||
setGetData(data);
|
console.log("sadadddd", response2?.data?.data.length);
|
||||||
|
for (let i = 0; i < response2?.data?.data.length; i++) {
|
||||||
|
for (let j = 0; j < temp.length; j++) {
|
||||||
|
console.log("temp", j, temp[j].id);
|
||||||
|
if (response2?.data?.data[i].mediaUploadId === temp[j].id) {
|
||||||
|
temp[j].staticPage = true;
|
||||||
|
} else {
|
||||||
|
temp[j].staticPage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("tesmasdasdasdasd", temp);
|
||||||
|
setGetData(temp);
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,18 +65,13 @@ import { listEnableCategory } from "@/service/content/content";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
import { data } from "jquery";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { setBanner } from "@/service/settings/settings";
|
|
||||||
import { id } from "date-fns/locale";
|
|
||||||
import CustomPagination from "@/components/table/custom-pagination";
|
|
||||||
|
|
||||||
const ContentListBanner = () => {
|
const ContentListTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [showData, setShowData] = React.useState("9");
|
const [showData, setShowData] = React.useState("10");
|
||||||
const [categories, setCategories] = React.useState<any>();
|
const [categories, setCategories] = React.useState<any>();
|
||||||
const [data, setData] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
|
@ -91,29 +86,31 @@ const ContentListBanner = () => {
|
||||||
});
|
});
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
const [selectedItems, setSelectedItems] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [searchQuery, setSearchQuery] = React.useState("");
|
const table = useReactTable({
|
||||||
const [bannerCount, setBannerCount] = React.useState<number>(0);
|
data: dataTable,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
let typingTimer: any;
|
||||||
fetchBannerCount();
|
const doneTypingInterval = 1500;
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function fetchBannerCount() {
|
|
||||||
try {
|
|
||||||
const res = await listDataMedia(0, "100", "", "", "");
|
|
||||||
const banners = res?.data?.data?.content?.filter(
|
|
||||||
(item: any) => item.isBanner
|
|
||||||
);
|
|
||||||
setBannerCount(banners?.length || 0);
|
|
||||||
|
|
||||||
setBannerCount(data?.length || 0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching banner count:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
const handleKeyUp = () => {
|
||||||
clearTimeout(typingTimer);
|
clearTimeout(typingTimer);
|
||||||
|
|
@ -125,21 +122,6 @@ const ContentListBanner = () => {
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
let typingTimer: NodeJS.Timeout;
|
|
||||||
const doneTypingInterval = 1500;
|
|
||||||
|
|
||||||
const handleTyping = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(() => {
|
|
||||||
setPage(1);
|
|
||||||
fetchData();
|
|
||||||
}, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [categoryFilter, statusFilter]);
|
|
||||||
|
|
||||||
async function doneTyping() {
|
async function doneTyping() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +135,10 @@ const ContentListBanner = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
// setPagination({
|
setPagination({
|
||||||
// pageIndex: 0,
|
pageIndex: 0,
|
||||||
// pageSize: Number(showData),
|
pageSize: Number(showData),
|
||||||
// });
|
});
|
||||||
}, [page, showData]);
|
}, [page, showData]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
|
@ -165,11 +147,10 @@ const ContentListBanner = () => {
|
||||||
const res = await listDataMedia(
|
const res = await listDataMedia(
|
||||||
page - 1,
|
page - 1,
|
||||||
showData,
|
showData,
|
||||||
searchQuery,
|
"",
|
||||||
categoryFilter?.sort().join(","),
|
categoryFilter?.sort().join(","),
|
||||||
statusFilter?.sort().join(",")
|
statusFilter?.sort().join(",")
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
|
|
@ -178,7 +159,7 @@ const ContentListBanner = () => {
|
||||||
|
|
||||||
console.log("contentData : ", data);
|
console.log("contentData : ", data);
|
||||||
|
|
||||||
setData(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
close();
|
close();
|
||||||
|
|
@ -219,73 +200,17 @@ const ContentListBanner = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = (id: number) => {
|
|
||||||
setSelectedItems((prev) =>
|
|
||||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
|
||||||
if (selectedItems.length === data.length) {
|
|
||||||
setSelectedItems([]);
|
|
||||||
} else {
|
|
||||||
setSelectedItems(data.map((item: any) => Number(item.id)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const handleBanner = async (ids: number[]) => {
|
|
||||||
try {
|
|
||||||
// const res = await Promise.all(ids.map((id) => setBanner(id, true)));
|
|
||||||
|
|
||||||
for (const element of ids) {
|
|
||||||
loading();
|
|
||||||
const res = await setBanner(element, true);
|
|
||||||
close();
|
|
||||||
if (res?.error) {
|
|
||||||
toast({
|
|
||||||
title: "Gagal",
|
|
||||||
description:
|
|
||||||
"Banner sudah melebihi batas maksimum (4 konten). Silahkan di disable banner Lainnya.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: "Sukses",
|
|
||||||
description: `item berhasil dijadikan banner.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: "Gagal",
|
|
||||||
description: "Terjadi kesalahan saat menjadikan banner.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between ">
|
<div className="flex justify-between ">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={searchQuery}
|
onKeyUp={handleKeyUp}
|
||||||
onChange={(e) => {
|
onKeyDown={handleKeyDown}
|
||||||
setSearchQuery(e.target.value);
|
|
||||||
handleTyping();
|
|
||||||
}}
|
|
||||||
className="max-w-[300px]"
|
className="max-w-[300px]"
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
{/* <div className="flex flex-row gap-2">
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size="md" variant="outline">
|
<Button size="md" variant="outline">
|
||||||
|
|
@ -413,65 +338,56 @@ const ContentListBanner = () => {
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
{/* Header select all + action */}
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedItems.length === data.length}
|
|
||||||
onCheckedChange={handleSelectAll}
|
|
||||||
/>
|
|
||||||
<span className="text-black dark:text-white">Pilih Semua</span>
|
|
||||||
</div>
|
</div>
|
||||||
{selectedItems.length > 0 && (
|
<Table className="overflow-hidden">
|
||||||
<Button color="primary" onClick={() => handleBanner(selectedItems)}>
|
<TableHeader>
|
||||||
Jadikan Banner
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
</Button>
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
)}
|
)}
|
||||||
</div>
|
</TableHead>
|
||||||
|
|
||||||
{/* Grid Cards */}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
||||||
{data?.map((item: any) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="relative rounded-lg shadow-md overflow-hidden border border-gray-200"
|
|
||||||
>
|
|
||||||
<div className="absolute top-2 left-2 z-10">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedItems.includes(item.id)}
|
|
||||||
onCheckedChange={() => handleSelect(item.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
src={item.smallThumbnailLink || "/placeholder.jpg"}
|
|
||||||
alt={item.title}
|
|
||||||
className="w-full h-48 object-cover"
|
|
||||||
/>
|
|
||||||
<Link
|
|
||||||
href={`/contributor/content/image/detail/${item?.id}`}
|
|
||||||
className="p-3"
|
|
||||||
>
|
|
||||||
<h4 className="font-semibold text-sm">{item.title}</h4>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</TableRow>
|
||||||
<div className="mt-3">
|
))}
|
||||||
{data && data?.length > 0 ? (
|
</TableHeader>
|
||||||
<CustomPagination
|
<TableBody>
|
||||||
totalPage={totalPage}
|
{table.getRowModel().rows?.length ? (
|
||||||
onPageChange={(data) => setPage(data)}
|
table.getRowModel().rows.map((row) => (
|
||||||
/>
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="h-[75px]"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
<p>No Data</p>
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
</div>
|
</TableBody>
|
||||||
</div>
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContentListBanner;
|
export default ContentListTable;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { useState } from "react";
|
||||||
|
|
||||||
import BannerListTable from "./component/banner-table";
|
import BannerListTable from "./component/banner-table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import ContentListBanner from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminBanner() {
|
export default function AdminBanner() {
|
||||||
const [selectedTab, setSelectedTab] = useState("content");
|
const [selectedTab, setSelectedTab] = useState("content");
|
||||||
|
|
@ -13,7 +12,7 @@ export default function AdminBanner() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
{selectedTab === "content" ? "List Media" : " List Banner"}
|
{selectedTab === "content" ? "List Media" : " List Banner"}
|
||||||
|
|
||||||
|
|
@ -28,7 +27,7 @@ export default function AdminBanner() {
|
||||||
: "bg-white text-black "
|
: "bg-white text-black "
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Konten
|
Kontent
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
rounded="md"
|
rounded="md"
|
||||||
|
|
@ -45,11 +44,7 @@ export default function AdminBanner() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedTab === "content" ? (
|
{selectedTab === "content" ? <ContentListTable /> : <BannerListTable />}
|
||||||
<ContentListBanner />
|
|
||||||
) : (
|
|
||||||
<BannerListTable />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,17 +45,19 @@ const columns: ColumnDef<any>[] = [
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "mediaTypesString",
|
accessorKey: "contentType",
|
||||||
header: "Tipe Konten",
|
header: "Tipe Konten",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("mediaTypesString")}</span>
|
<span className="normal-case">{row.getValue("contentType")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "publishedLocation",
|
accessorKey: "isInternational",
|
||||||
header: "Wilayah",
|
header: "Wilayah",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("publishedLocation")}</span>
|
<span className="normal-case">
|
||||||
|
{row.getValue("isInternational") ? "INT" : "ID"}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -67,16 +69,6 @@ const columns: ColumnDef<any>[] = [
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "created",
|
|
||||||
accessorKey: "created",
|
|
||||||
header: "Dibuat Oleh",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<span className="text-sm text-gray-500">{row.original.createdByName}</span><span className="text-xs font-bold">({row.original.createdByUserLevelName})</span>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
|
|
@ -100,7 +92,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
router.push("/admin/settings/category?dataChange=true");
|
router.push("/admin/settings/category?dataChange=true");
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Menubar className="border-none dark:bg-black">
|
<Menubar className="border-none">
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>
|
<MenubarTrigger>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -125,16 +125,16 @@ export default function CreateCategoryModal() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentType = form.watch("contentType");
|
const contentType = form.watch("contentType");
|
||||||
const isAllContentChecked = contentType && listContent.every((item) =>
|
const isAllContentChecked = listContent.every((item) =>
|
||||||
contentType.includes(item.id)
|
contentType?.includes(item.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const users = form.watch("selectedUser");
|
const users = form.watch("selectedUser");
|
||||||
const isAllUserChecked = users && userList.every((item) => users.includes(item.id));
|
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
|
||||||
|
|
||||||
const target = form.watch("publishTo");
|
const target = form.watch("publishTo");
|
||||||
const isAllTargetChecked = target && publishToList.every((item) =>
|
const isAllTargetChecked = publishToList.every((item) =>
|
||||||
target.includes(item.id)
|
target?.includes(item.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
|
@ -240,7 +240,7 @@ export default function CreateCategoryModal() {
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button color="primary" size="md">
|
<Button color="primary" size="md">
|
||||||
{t("add-category", { defaultValue: "Add Category" })}
|
{t("add-category")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
|
|
@ -248,12 +248,12 @@ export default function CreateCategoryModal() {
|
||||||
className="sm:h-[300px] md:h-[300px] lg:h-[500px] overflow-y-auto"
|
className="sm:h-[300px] md:h-[300px] lg:h-[500px] overflow-y-auto"
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle> {t("add-category", { defaultValue: "Add Category" })}</DialogTitle>
|
<DialogTitle> {t("add-category")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white dark:bg-[#1f2937] rounded-sm"
|
className="space-y-3 bg-white rounded-sm"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|
|
||||||
|
|
@ -138,34 +138,35 @@ export default function EditCategoryModal(props: {
|
||||||
maxFiles: 1,
|
maxFiles: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// initFetch();
|
|
||||||
// }, [id]);
|
|
||||||
|
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const req = await getCategoryDetail(id);
|
const req = await getCategoryDetail(id);
|
||||||
const data = req?.data?.data;
|
const data = req?.data?.data;
|
||||||
|
console.log("dataC", data);
|
||||||
form.setValue("id", String(data?.id));
|
form.setValue("id", String(data?.id));
|
||||||
form.setValue("title", String(data?.name));
|
form.setValue("title", String(data?.name));
|
||||||
form.setValue("description", String(data?.description));
|
form.setValue("description", String(data?.description));
|
||||||
form.setValue("contentType", data?.mediaTypes?.split(",") || []);
|
form.setValue("contentType", data?.mediaTypes?.split(","));
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"selectedUser",
|
"selectedUser",
|
||||||
removeAndReturn(data?.publishedFor, [2, 3, 4])
|
removeAndReturn(data?.publishedFor, [2, 3, 4])
|
||||||
);
|
);
|
||||||
form.setValue("publishTo", data?.publishedLocation?.split(",") || []);
|
form.setValue("publishTo", data?.publishedLocation?.split(","));
|
||||||
form.setValue("file", thumbnailLink);
|
form.setValue("file", thumbnailLink);
|
||||||
|
|
||||||
setUnitData(filterString(data?.publishedLocationLevel, "under"));
|
setUnitData(filterString(data?.publishedLocationLevel, "under"));
|
||||||
setSatkerData(filterString(data?.publishedLocationLevel, "above"));
|
setSatkerData(filterString(data?.publishedLocationLevel, "above"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
initFetch();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
function removeAndReturn(inputString: string, toRemove: number[]) {
|
function removeAndReturn(inputString: string, toRemove: number[]) {
|
||||||
const numbers = inputString?.split(",").map(Number);
|
const numbers = inputString.split(",").map(Number);
|
||||||
|
|
||||||
const filteredNumbers = numbers?.filter((num) => !toRemove?.includes(num));
|
const filteredNumbers = numbers.filter((num) => !toRemove.includes(num));
|
||||||
|
|
||||||
return filteredNumbers?.map(String);
|
return filteredNumbers.map(String);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterString(inputString: string, type: string) {
|
function filterString(inputString: string, type: string) {
|
||||||
|
|
@ -182,16 +183,17 @@ export default function EditCategoryModal(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = form.watch("contentType");
|
const contentType = form.watch("contentType");
|
||||||
const isAllContentChecked =
|
const isAllContentChecked = listContent.every((item) =>
|
||||||
contentType && listContent.every((item) => contentType.includes(item.id));
|
contentType?.includes(item.id)
|
||||||
|
);
|
||||||
|
|
||||||
const users = form.watch("selectedUser");
|
const users = form.watch("selectedUser");
|
||||||
const isAllUserChecked =
|
const isAllUserChecked = userList.every((item) => users?.includes(item.id));
|
||||||
users && userList.every((item) => users.includes(item.id));
|
|
||||||
|
|
||||||
const target = form.watch("publishTo");
|
const target = form.watch("publishTo");
|
||||||
const isAllTargetChecked =
|
const isAllTargetChecked = publishToList.every((item) =>
|
||||||
target && publishToList.every((item) => target.includes(item.id));
|
target?.includes(item.id)
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRoles();
|
getRoles();
|
||||||
|
|
@ -211,7 +213,6 @@ export default function EditCategoryModal(props: {
|
||||||
const uniqueNumbers = Array.from(new Set(numbers));
|
const uniqueNumbers = Array.from(new Set(numbers));
|
||||||
return uniqueNumbers.join(",");
|
return uniqueNumbers.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
const formMedia = new FormData();
|
const formMedia = new FormData();
|
||||||
|
|
||||||
|
|
@ -262,9 +263,7 @@ export default function EditCategoryModal(props: {
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<a
|
<a
|
||||||
onClick={() =>{
|
onClick={() => setIsOpen(true)}
|
||||||
initFetch()
|
|
||||||
setIsOpen(true); } }
|
|
||||||
className="hover:underline cursor-pointer"
|
className="hover:underline cursor-pointer"
|
||||||
>
|
>
|
||||||
{isDetail ? "Detail" : "Edit"}
|
{isDetail ? "Detail" : "Edit"}
|
||||||
|
|
|
||||||
|
|
@ -10,34 +10,15 @@ export default function StatusToogle(props: {
|
||||||
}) {
|
}) {
|
||||||
const { id, initValue } = props;
|
const { id, initValue } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// const publishCategory = async (id: number, status: string) => {
|
|
||||||
// const response = await publishUnpublishCategory(id, status);
|
|
||||||
// console.log(response);
|
|
||||||
// if (response?.error) {
|
|
||||||
// error(response.message);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// router.push("/admin/settings/category?dataChange=true");
|
|
||||||
// };
|
|
||||||
const publishCategory = async (id: number, status: string) => {
|
const publishCategory = async (id: number, status: string) => {
|
||||||
const response = await publishUnpublishCategory(id, status);
|
const response = await publishUnpublishCategory(id, status);
|
||||||
console.log("API Response:", response);
|
console.log(response);
|
||||||
|
|
||||||
// cek error interceptor
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response.message || "Terjadi kesalahan");
|
error(response.message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cek flag success asli dari backend
|
|
||||||
if (response?.data?.success === false) {
|
|
||||||
error(response?.data?.message || "Terjadi kesalahan");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push("/admin/settings/category?dataChange=true");
|
router.push("/admin/settings/category?dataChange=true");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
id={String(id)}
|
id={String(id)}
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,23 @@ import {
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./column";
|
import columns from "./column";
|
||||||
|
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { NewCampaignIcon } from "@/components/icon";
|
||||||
import { getCategories } from "@/service/settings/settings";
|
import { getCategories } from "@/service/settings/settings";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import CreateCategoryModal from "./create";
|
import CreateCategoryModal from "./create";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
|
|
||||||
const AdminCategoryTable = () => {
|
const AdminCategoryTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -49,9 +55,6 @@ const AdminCategoryTable = () => {
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [region, setRegion] = React.useState<any>();
|
|
||||||
const [regionFilter, setRegionFilter] = React.useState<string[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<string[]>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
@ -86,14 +89,6 @@ const AdminCategoryTable = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const manualRegions = [
|
|
||||||
{ id: "semua", label: "Semua" },
|
|
||||||
{ id: "mabes", label: "Mabes" },
|
|
||||||
{ id: "polda", label: "Polda" },
|
|
||||||
{ id: "satker", label: "Satker" },
|
|
||||||
{ id: "internasional", label: "Internasional" },
|
|
||||||
];
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (dataChange) {
|
if (dataChange) {
|
||||||
router.push("/admin/settings/category");
|
router.push("/admin/settings/category");
|
||||||
|
|
@ -110,28 +105,14 @@ const AdminCategoryTable = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, regionFilter]);
|
}, [page]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
loading();
|
loading();
|
||||||
|
const response = await getCategories(page - 1);
|
||||||
const regionQuery = regionFilter.length
|
|
||||||
? `&publishedLocation=${regionFilter.join(",")}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const statusQuery = statusFilter.length
|
|
||||||
? `&isPublish=${statusFilter.join(",")}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const response = await getCategories(
|
|
||||||
page - 1,
|
|
||||||
`${regionQuery}${statusQuery}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = response?.data?.data;
|
const data = response?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content;
|
||||||
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * 10 + index + 1;
|
item.no = (page - 1) * 10 + index + 1;
|
||||||
});
|
});
|
||||||
|
|
@ -141,124 +122,17 @@ const AdminCategoryTable = () => {
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
close();
|
close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching categories:", error);
|
console.error("Error fetching tasks:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (type: string, id: string, checked: boolean) => {
|
|
||||||
if (type === "region") {
|
|
||||||
if (id === "semua") {
|
|
||||||
if (checked) {
|
|
||||||
// Pilih semua (kecuali 'semua' itu sendiri)
|
|
||||||
setRegionFilter(["mabes", "polda", "satker", "internasional"]);
|
|
||||||
} else {
|
|
||||||
setRegionFilter([]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let updated = checked
|
|
||||||
? [...regionFilter, id]
|
|
||||||
: regionFilter.filter((val) => val !== id);
|
|
||||||
|
|
||||||
// Jika semua sudah tercentang, maka otomatis centang "semua"
|
|
||||||
const allIds = ["mabes", "polda", "satker", "internasional"];
|
|
||||||
const allSelected = allIds.every((val) => updated.includes(val));
|
|
||||||
|
|
||||||
setRegionFilter(allSelected ? allIds : updated);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checked) {
|
|
||||||
setStatusFilter([...statusFilter, id]);
|
|
||||||
} else {
|
|
||||||
setStatusFilter(statusFilter.filter((val: any) => val !== id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">{t("category", { defaultValue: "Category" })}</p>
|
<p className="text-xl font-medium text-default-900">{t("category")}</p>
|
||||||
<CreateCategoryModal />
|
<CreateCategoryModal />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end justify-end">
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 ">
|
|
||||||
<div className="flex flex-col gap-2 px-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<p>Filter</p>
|
|
||||||
<a
|
|
||||||
onClick={() => fetchData()}
|
|
||||||
className="cursor-pointer text-primary"
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
|
||||||
<p>Wilayah</p>
|
|
||||||
{manualRegions.map((region) => (
|
|
||||||
<div key={region.id} className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={region.id}
|
|
||||||
checked={
|
|
||||||
region.id === "semua"
|
|
||||||
? ["mabes", "polda", "satker", "internasional"].every(
|
|
||||||
(val) => regionFilter.includes(val)
|
|
||||||
)
|
|
||||||
: regionFilter.includes(region.id)
|
|
||||||
}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("region", region.id, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={region.id}
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{region.label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<p className="mt-3">Status</p>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="aktif"
|
|
||||||
checked={statusFilter.includes("true")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", "true", Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="aktif"
|
|
||||||
className="text-xs font-medium leading-none"
|
|
||||||
>
|
|
||||||
Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="nonaktif"
|
|
||||||
checked={statusFilter.includes("false")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", "false", Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="nonaktif"
|
|
||||||
className="text-xs font-medium leading-none"
|
|
||||||
>
|
|
||||||
Non Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<Table className="overflow-hidden">
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,11 @@ export function UnitMapping(props: {
|
||||||
|
|
||||||
const unitType = form.watch("items");
|
const unitType = form.watch("items");
|
||||||
|
|
||||||
const isAllUnitChecked = unitType && unitList.every((item) =>
|
const isAllUnitChecked = unitList.every((item) =>
|
||||||
unitType.includes(String(item.id))
|
unitType?.includes(String(item.id))
|
||||||
);
|
);
|
||||||
const isAllSatkerChecked = unitType && satkerList.every((item) =>
|
const isAllSatkerChecked = satkerList.every((item) =>
|
||||||
unitType.includes(String(item.id))
|
unitType?.includes(String(item.id))
|
||||||
);
|
);
|
||||||
|
|
||||||
const setupUnit = (data: UnitType[]) => {
|
const setupUnit = (data: UnitType[]) => {
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ export default function CreateFAQModal() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const target = form.watch("publishTo");
|
const target = form.watch("publishTo");
|
||||||
const isAllTargetChecked = target && publishToList.every((item) =>
|
const isAllTargetChecked = publishToList.every((item) =>
|
||||||
target.includes(item.id)
|
target?.includes(item.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
|
|
@ -121,12 +121,12 @@ export default function CreateFAQModal() {
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button color="primary" size="md">
|
<Button color="primary" size="md">
|
||||||
{t("add", { defaultValue: "Add" })} FAQ
|
{t("add")} FAQ
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("add", { defaultValue: "Add" })} FAQ</DialogTitle>
|
<DialogTitle>{t("add")} FAQ</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|
|
||||||
|
|
@ -111,12 +111,12 @@ export default function CreateFAQModal() {
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button color="primary" size="md">
|
<Button color="primary" size="md">
|
||||||
{t("add", { defaultValue: "Add" })} Feedback
|
{t("add")} Feedback
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("add", { defaultValue: "Add" })} Feedback</DialogTitle>
|
<DialogTitle>{t("add")} Feedback</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
import { deleteAdvertisements, setBanner } from "@/service/settings/settings";
|
|
||||||
import { error } from "@/config/swal";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import { deleteMedia } from "@/service/content/content";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => <div className="w-[150px]">{row.getValue("title")}</div>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "contentFileName",
|
|
||||||
header: "Judul Gambar",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="w-[450px]">{row.getValue("contentFileName")}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "placements",
|
|
||||||
header: "Posisi",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="w-[150px]">{row.getValue("placements")}</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "statusName",
|
|
||||||
header: "Status",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("statusName")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Actions",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
const levelNumber = getCookiesDecrypt("ulne");
|
|
||||||
async function doDelete(id: any) {
|
|
||||||
// loading();
|
|
||||||
const data = {
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await deleteAdvertisements(id);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
|
|
||||||
function success() {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Sukses",
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteAdvertisements = (id: any) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Hapus Data",
|
|
||||||
text: "",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonColor: "#3085d6",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
confirmButtonText: "Hapus",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
doDelete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
{/* <DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link href={`/admin/settings/iklan/detail/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<Link href={`/admin/settings/iklan/update/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
|
||||||
Edit
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteAdvertisements(row.original.id)}
|
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent> */}
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link href={`/admin/settings/iklan/detail/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{levelNumber === "1" && (
|
|
||||||
<>
|
|
||||||
<Link href={`/admin/settings/iklan/update/${row.original.id}`}>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
|
||||||
Edit
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteAdvertisements(row.original.id)}
|
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
import StatusToogle from "./status-toogle";
|
|
||||||
import StaticToogle from "./static-toogle";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
// {
|
|
||||||
// accessorKey: "no",
|
|
||||||
// header: "No",
|
|
||||||
// cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
// },
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "categoryName",
|
|
||||||
header: "Kategori",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "createdAt",
|
|
||||||
header: "Tanggal Unggah",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "isStaticBanner",
|
|
||||||
header: "Static Banner",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<StaticToogle
|
|
||||||
id={row.original.id}
|
|
||||||
initChecked={row.original.isStaticBanner}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "statusName",
|
|
||||||
header: "Status Banner",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Actions",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Link
|
|
||||||
href={`/contributor/content/image/detail/${row.original.id}`}
|
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import columns from "./popup-column";
|
|
||||||
import { listBanner, listStaticBanner } from "@/service/settings/settings";
|
|
||||||
|
|
||||||
const IklanListTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [getData, setGetData] = React.useState<any>([]);
|
|
||||||
const dataChange = searchParams?.get("dataChange");
|
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const table = useReactTable({
|
|
||||||
data: getData,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (dataChange) {
|
|
||||||
router.push("/admin/settings/banner");
|
|
||||||
}
|
|
||||||
getListBanner();
|
|
||||||
}, [dataChange]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getListBanner();
|
|
||||||
// getListStaticBanner();
|
|
||||||
}, [page, showData]);
|
|
||||||
|
|
||||||
async function getListBanner() {
|
|
||||||
loading();
|
|
||||||
let temp: any;
|
|
||||||
|
|
||||||
const response = await listBanner();
|
|
||||||
const data = response?.data?.data?.content;
|
|
||||||
console.log("banner", data);
|
|
||||||
setGetData(data);
|
|
||||||
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Table className="overflow-hidden mt-10">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className="h-[75px]"
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IklanListTable;
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { setStaticBanner } from "@/service/settings/settings";
|
|
||||||
|
|
||||||
export default function StaticToogle(props: {
|
|
||||||
id: number;
|
|
||||||
initChecked: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, initChecked } = props;
|
|
||||||
const { toast } = useToast();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const changeStaticBanner = async (id: number) => {
|
|
||||||
const response = await setStaticBanner(id);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: response?.message,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success ",
|
|
||||||
});
|
|
||||||
router.push("/admin/settings/banner?dataChange=true");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
id="static-toogle"
|
|
||||||
checked={initChecked}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
changeStaticBanner(id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { setBanner } from "@/service/settings/settings";
|
|
||||||
|
|
||||||
export default function StatusToogle(props: {
|
|
||||||
id: number;
|
|
||||||
initChecked: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, initChecked } = props;
|
|
||||||
const { toast } = useToast();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const disableBanner = async () => {
|
|
||||||
const response = await setBanner(id, false);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: response?.message,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success ",
|
|
||||||
});
|
|
||||||
router.push("/admin/settings/banner?dataChange=true");
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
id="status-toogle"
|
|
||||||
checked={initChecked}
|
|
||||||
onCheckedChange={() => disableBanner()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,420 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Plus,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UploadIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
|
||||||
import { paginationBlog } from "@/service/blog/blog";
|
|
||||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import columns from "./column";
|
|
||||||
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import {
|
|
||||||
listDataAdvertisements,
|
|
||||||
listDataMedia,
|
|
||||||
} from "@/service/broadcast/broadcast";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import { TambahIklanModal } from "@/components/form/setting/form-add-iklan";
|
|
||||||
|
|
||||||
const AdvertisementsList = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const roleId = getCookiesDecrypt("urie");
|
|
||||||
const levelNumber = getCookiesDecrypt("ulne");
|
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
|
||||||
const table = useReactTable({
|
|
||||||
data: dataTable,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let typingTimer: any;
|
|
||||||
const doneTypingInterval = 1500;
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function doneTyping() {
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
setPagination({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
}, [page, showData]);
|
|
||||||
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
const res = await listDataAdvertisements(page - 1, showData, "");
|
|
||||||
const data = res?.data?.data;
|
|
||||||
const contentData = data?.content;
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("contentData : ", data);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
close();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching tasks:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function getCategories() {
|
|
||||||
const category = await listEnableCategory("");
|
|
||||||
const resCategory = category?.data?.data?.content;
|
|
||||||
setCategories(resCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (type: string, id: number, checked: boolean) => {
|
|
||||||
if (type === "category") {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...categoryFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = categoryFilter.filter((a) => a !== id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...statusFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = statusFilter.filter((a) => a !== id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
{levelNumber === "1" && (
|
|
||||||
<div className="flex-none">
|
|
||||||
<Link href={"/admin/settings/iklan/create"}>
|
|
||||||
<Button
|
|
||||||
disabled={dataTable.length == 4}
|
|
||||||
color="primary"
|
|
||||||
className="text-white"
|
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
<UploadIcon size={18} className="mr-2" />
|
|
||||||
Tambah Iklan
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
{dataTable.length == 4 && (
|
|
||||||
<p className="text-sm text-red-400 pt-1">
|
|
||||||
Jumlah Iklan Sudah Maksimal (4)
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* <TambahIklanModal /> */}
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between ">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search"
|
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
className="max-w-[300px]"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-row gap-2">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
1 - {showData} Data
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 text-sm">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={showData}
|
|
||||||
onValueChange={setShowData}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="10">
|
|
||||||
1 - 10 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="20">
|
|
||||||
1 - 20 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="25">
|
|
||||||
1 - 25 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="50">
|
|
||||||
1 - 50 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 ">
|
|
||||||
<div className="flex flex-col gap-2 px-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<p>Filter</p>
|
|
||||||
<a
|
|
||||||
onClick={() => fetchData()}
|
|
||||||
className="cursor-pointer text-primary"
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
|
||||||
<p>Kategory</p>
|
|
||||||
{categories?.map((category: any) => (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={category.id}
|
|
||||||
checked={categoryFilter.includes(category.id)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("category", category.id, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={category.id}
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{category.name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<p className="mt-3">Status</p>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(1)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 1, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Menunggu Review
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(2)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 2, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Diterima
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(3)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 3, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Minta Update{" "}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(4)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 4, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Ditolak
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Table className="overflow-hidden">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className="h-[75px]"
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvertisementsList;
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import FormTask from "@/components/form/task/task-form";
|
|
||||||
import FormPressConference from "@/components/form/schedule/press-conference-form";
|
|
||||||
import { CalendarPolriAdd } from "@/components/form/schedule/form-calendar-polri";
|
|
||||||
import { TambahIklanModal } from "@/components/form/setting/form-add-iklan";
|
|
||||||
|
|
||||||
const AdvertisementsCreatePage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<TambahIklanModal />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvertisementsCreatePage;
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import { TambahIklanDetail } from "@/components/form/setting/form-add-iklan-detail";
|
|
||||||
|
|
||||||
const AdvertisementsDetailPage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<TambahIklanDetail />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvertisementsDetailPage;
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
"use client";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import { useState } from "react";
|
|
||||||
import AdvertisementsList from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminAdvertisements() {
|
|
||||||
const [selectedTab, setSelectedTab] = useState("content");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
|
||||||
<AdvertisementsList />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import { TambahIklanUpdate } from "@/components/form/setting/form-add-iklan-update";
|
|
||||||
|
|
||||||
const AdvertisementsUpdatePage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<TambahIklanUpdate />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdvertisementsUpdatePage;
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
formatDateToIndonesian,
|
|
||||||
getOnlyDate,
|
|
||||||
htmlToString,
|
|
||||||
} from "@/utils/globals";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
|
||||||
import StatusToogle from "./status-toogle";
|
|
||||||
import StaticToogle from "./static-toogle";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
// {
|
|
||||||
// accessorKey: "no",
|
|
||||||
// header: "No",
|
|
||||||
// cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
// },
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "title",
|
|
||||||
header: "Judul",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "categoryName",
|
|
||||||
header: "Kategori",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "createdAt",
|
|
||||||
header: "Tanggal Unggah",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "statusName",
|
|
||||||
header: "Status Pop Up",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<StatusToogle id={row.original.id} initChecked={row.original.isInterstitial} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: "Actions",
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Link
|
|
||||||
href={`/contributor/content/image/detail/${row.original.id}`}
|
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default columns;
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import columns from "./popup-column";
|
|
||||||
import { getListPopUp, listBanner, listStaticBanner } from "@/service/settings/settings";
|
|
||||||
import { listDataPopUp } from "@/service/broadcast/broadcast";
|
|
||||||
|
|
||||||
const PopUpListTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
|
||||||
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [getData, setGetData] = React.useState<any>([]);
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const dataChange = searchParams?.get("dataChange");
|
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const table = useReactTable({
|
|
||||||
data: getData,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (dataChange) {
|
|
||||||
router.push("/admin/settings/popup");
|
|
||||||
}
|
|
||||||
getPopUp();
|
|
||||||
}, [dataChange]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getPopUp();
|
|
||||||
// getListStaticBanner();
|
|
||||||
}, [page, showData]);
|
|
||||||
|
|
||||||
async function getPopUp() {
|
|
||||||
loading();
|
|
||||||
let temp: any;
|
|
||||||
|
|
||||||
const response = await getListPopUp();
|
|
||||||
const data = response?.data?.data?.content;
|
|
||||||
console.log("banner", data);
|
|
||||||
setGetData(data);
|
|
||||||
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{table &&
|
|
||||||
<Table className="overflow-hidden mt-10">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table?.getRowModel()?.rows?.length ? (
|
|
||||||
table?.getRowModel()?.rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className="h-[75px]"
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PopUpListTable;
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { setStaticBanner } from "@/service/settings/settings";
|
|
||||||
|
|
||||||
export default function StaticToogle(props: {
|
|
||||||
id: number;
|
|
||||||
initChecked: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, initChecked } = props;
|
|
||||||
const { toast } = useToast();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const changeStaticBanner = async (id: number) => {
|
|
||||||
const response = await setStaticBanner(id);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: response?.message,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success ",
|
|
||||||
});
|
|
||||||
router.push("/admin/settings/banner?dataChange=true");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
id="static-toogle"
|
|
||||||
checked={initChecked}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
changeStaticBanner(id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { setBanner, setPopUp } from "@/service/settings/settings";
|
|
||||||
|
|
||||||
export default function StatusToogle(props: {
|
|
||||||
id: number;
|
|
||||||
initChecked: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, initChecked } = props;
|
|
||||||
const { toast } = useToast();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const disableBanner = async () => {
|
|
||||||
const response = await setPopUp(id, false);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: response?.message,
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success ",
|
|
||||||
});
|
|
||||||
router.push("/admin/settings/popup?dataChange=true");
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
id="status-toogle"
|
|
||||||
checked={initChecked}
|
|
||||||
onCheckedChange={() => disableBanner()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,495 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
|
||||||
import { paginationBlog } from "@/service/blog/blog";
|
|
||||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import columns from "./column";
|
|
||||||
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { listDataMedia, listDataPopUp } from "@/service/broadcast/broadcast";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link } from "@/i18n/routing";
|
|
||||||
import { data } from "jquery";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { setBanner, setPopUp } from "@/service/settings/settings";
|
|
||||||
import { id } from "date-fns/locale";
|
|
||||||
import CustomPagination from "@/components/table/custom-pagination";
|
|
||||||
|
|
||||||
const ContentListPopUp = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const [showData, setShowData] = React.useState("9");
|
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
const [data, setData] = React.useState<any[]>([]);
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
|
||||||
const [selectedItems, setSelectedItems] = React.useState<number[]>([]);
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const [searchQuery, setSearchQuery] = React.useState("");
|
|
||||||
|
|
||||||
let typingTimer: NodeJS.Timeout;
|
|
||||||
const doneTypingInterval = 2000;
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = () => {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(() => {
|
|
||||||
setPage(1);
|
|
||||||
fetchData();
|
|
||||||
}, doneTypingInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [categoryFilter, statusFilter]);
|
|
||||||
|
|
||||||
async function doneTyping() {
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
setPagination({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: Number(showData),
|
|
||||||
});
|
|
||||||
}, [page, showData]);
|
|
||||||
|
|
||||||
// async function fetchData() {
|
|
||||||
// try {
|
|
||||||
// loading();
|
|
||||||
// const res = await listDataPopUp(
|
|
||||||
// page - 1,
|
|
||||||
// showData,
|
|
||||||
// "",
|
|
||||||
// categoryFilter?.sort().join(","),
|
|
||||||
// statusFilter?.sort().join(",")
|
|
||||||
// );
|
|
||||||
// const data = res?.data?.data;
|
|
||||||
// const contentData = data?.content;
|
|
||||||
// contentData.forEach((item: any, index: number) => {
|
|
||||||
// item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// console.log("contentData : ", data);
|
|
||||||
|
|
||||||
// setData(contentData);
|
|
||||||
// setTotalData(data?.totalElements);
|
|
||||||
// setTotalPage(data?.totalPages);
|
|
||||||
// close();
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error("Error fetching tasks:", error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
const res = await listDataMedia(
|
|
||||||
page - 1,
|
|
||||||
showData,
|
|
||||||
searchQuery, // <-- gunakan nilai pencarian
|
|
||||||
categoryFilter?.sort().join(","),
|
|
||||||
statusFilter?.sort().join(",")
|
|
||||||
);
|
|
||||||
const data = res?.data?.data;
|
|
||||||
const contentData = data?.content;
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("contentData : ", data);
|
|
||||||
|
|
||||||
setData(contentData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
close();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching tasks:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
getCategories();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
async function getCategories() {
|
|
||||||
const category = await listEnableCategory("");
|
|
||||||
const resCategory = category?.data?.data?.content;
|
|
||||||
setCategories(resCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (type: string, id: number, checked: boolean) => {
|
|
||||||
if (type === "category") {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...categoryFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = categoryFilter.filter((a) => a !== id);
|
|
||||||
setCategoryFilter(temp);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checked) {
|
|
||||||
const temp: number[] = [...statusFilter];
|
|
||||||
temp.push(id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
} else {
|
|
||||||
const temp = statusFilter.filter((a) => a !== id);
|
|
||||||
setStatusFilter(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (id: number) => {
|
|
||||||
setSelectedItems((prev) =>
|
|
||||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
|
||||||
if (selectedItems.length === data.length) {
|
|
||||||
setSelectedItems([]);
|
|
||||||
} else {
|
|
||||||
setSelectedItems(data.map((item: any) => Number(item.id)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
// const handlePopUp = async (ids: number[]) => {
|
|
||||||
// try {
|
|
||||||
// await Promise.all(ids.map((id) => setPopUp(id, true)));
|
|
||||||
// toast({
|
|
||||||
// title: "Sukses",
|
|
||||||
// description: `${ids.length} item berhasil dijadikan Popup.`,
|
|
||||||
// });
|
|
||||||
// } catch (err) {
|
|
||||||
// toast({
|
|
||||||
// title: "Gagal",
|
|
||||||
// description: "Terjadi kesalahan saat menjadikan Popup.",
|
|
||||||
// variant: "destructive",
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handlePopUp = async (ids: number[]) => {
|
|
||||||
try {
|
|
||||||
// const res = await Promise.all(ids.map((id) => setBanner(id, true)));
|
|
||||||
|
|
||||||
for (const element of ids) {
|
|
||||||
loading();
|
|
||||||
const res = await setPopUp(element, true);
|
|
||||||
close();
|
|
||||||
if (res?.error) {
|
|
||||||
toast({
|
|
||||||
title: "Gagal",
|
|
||||||
description:
|
|
||||||
"Banner sudah melebihi batas maksimum (4 konten). Silahkan di disable popup Lainnya.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: "Sukses",
|
|
||||||
description: `item berhasil dijadikan banner.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: "Gagal",
|
|
||||||
description: "Terjadi kesalahan saat menjadikan banner.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex justify-between ">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchQuery(e.target.value);
|
|
||||||
handleKeyDown();
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="max-w-[300px]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* <div className="flex flex-row gap-2">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
1 - {showData} Data
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 text-sm">
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={showData}
|
|
||||||
onValueChange={setShowData}
|
|
||||||
>
|
|
||||||
<DropdownMenuRadioItem value="10">
|
|
||||||
1 - 10 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="20">
|
|
||||||
1 - 20 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="25">
|
|
||||||
1 - 25 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
<DropdownMenuRadioItem value="50">
|
|
||||||
1 - 50 Data
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 ">
|
|
||||||
<div className="flex flex-col gap-2 px-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<p>Filter</p>
|
|
||||||
<a
|
|
||||||
onClick={() => fetchData()}
|
|
||||||
className="cursor-pointer text-primary"
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
|
||||||
<p>Kategory</p>
|
|
||||||
{categories?.map((category: any) => (
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={category.id}
|
|
||||||
checked={categoryFilter.includes(category.id)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("category", category.id, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={category.id}
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{category.name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<p className="mt-3">Status</p>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(1)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 1, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Menunggu Review
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(2)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 2, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Diterima
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(3)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 3, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Minta Update{" "}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={statusFilter.includes(4)}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", 4, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
Ditolak
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{/* Header select all + action */}
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedItems.length === data.length}
|
|
||||||
onCheckedChange={handleSelectAll}
|
|
||||||
/>
|
|
||||||
<span>Pilih Semua</span>
|
|
||||||
</div>
|
|
||||||
{selectedItems.length > 0 && (
|
|
||||||
<Button color="primary" onClick={() => handlePopUp(selectedItems)}>
|
|
||||||
Jadikan PopUp
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grid Cards */}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
||||||
{data?.map((item: any) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="relative rounded-lg shadow-md overflow-hidden border border-gray-200"
|
|
||||||
>
|
|
||||||
<div className="absolute top-2 left-2 z-10">
|
|
||||||
<Checkbox
|
|
||||||
checked={selectedItems.includes(item.id)}
|
|
||||||
onCheckedChange={() => handleSelect(item.id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
src={item.smallThumbnailLink || "/placeholder.jpg"}
|
|
||||||
alt={item.title}
|
|
||||||
className="w-full h-48 object-cover"
|
|
||||||
/>
|
|
||||||
<Link
|
|
||||||
href={`/contributor/content/image/detail/${item?.id}`}
|
|
||||||
className="p-3"
|
|
||||||
>
|
|
||||||
<h4 className="font-semibold text-sm">{item.title}</h4>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-3">
|
|
||||||
{data && data?.length > 0 ? (
|
|
||||||
<CustomPagination
|
|
||||||
totalPage={totalPage}
|
|
||||||
onPageChange={(data) => setPage(data)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p>No Data</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContentListPopUp;
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
"use client";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import ContentListTable from "../banner/component/table";
|
|
||||||
import BannerListTable from "../banner/component/banner-table";
|
|
||||||
import PopUpList from "./component/table";
|
|
||||||
import PopUpListTable from "./component/popup-table";
|
|
||||||
|
|
||||||
export default function AdminPopup() {
|
|
||||||
const [selectedTab, setSelectedTab] = useState("content");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
{selectedTab === "content" ? "List Media" : " List Pop Up"}
|
|
||||||
|
|
||||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
|
||||||
<Button
|
|
||||||
rounded="md"
|
|
||||||
onClick={() => setSelectedTab("content")}
|
|
||||||
className={` hover:text-white
|
|
||||||
${
|
|
||||||
selectedTab === "content"
|
|
||||||
? "bg-black text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Konten
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
rounded="md"
|
|
||||||
onClick={() => setSelectedTab("popup")}
|
|
||||||
className={` hover:text-white
|
|
||||||
${
|
|
||||||
selectedTab === "popup"
|
|
||||||
? "bg-black text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Pop Up
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedTab === "content" ? <PopUpList /> : <PopUpListTable />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -14,7 +14,7 @@ import {
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { close, error, loading } from "@/config/swal";
|
import { close, error, loading } from "@/config/swal";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import JoditEditor from "jodit-react";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { getPrivacy, savePrivacy } from "@/service/settings/settings";
|
import { getPrivacy, savePrivacy } from "@/service/settings/settings";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
@ -104,6 +104,16 @@ export default function AdminPrivacyPolicy() {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Konten</FormLabel>
|
<FormLabel>Konten</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
{/* <JoditEditor
|
||||||
|
ref={editor}
|
||||||
|
value={field.value}
|
||||||
|
config={{
|
||||||
|
height: 400, // Tinggi editor dalam piksel
|
||||||
|
}}
|
||||||
|
className="dark:text-black"
|
||||||
|
onChange={field.onChange}
|
||||||
|
|
||||||
|
/> */}
|
||||||
<CustomEditor
|
<CustomEditor
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
initialData={field.value}
|
initialData={field.value}
|
||||||
|
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import { error } from "@/config/swal";
|
|
||||||
import { deleteCategory } from "@/service/settings/settings";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import {
|
|
||||||
Menubar,
|
|
||||||
MenubarContent,
|
|
||||||
MenubarMenu,
|
|
||||||
MenubarTrigger,
|
|
||||||
} from "@/components/ui/menubar";
|
|
||||||
import EditCategoryModal from "../../category/component/edit";
|
|
||||||
import withReactContent from "sweetalert2-react-content";
|
|
||||||
import { deleteMedia } from "@/service/content/content";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import DetailSettingTracking from "./detail";
|
|
||||||
import UpdateSettingTracking from "./update";
|
|
||||||
|
|
||||||
const useTableColumns = () => {
|
|
||||||
const t = useTranslations("Table"); // Panggil di dalam hook
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
const columns: ColumnDef<any>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: "no",
|
|
||||||
header: "No",
|
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
accessorKey: "name",
|
|
||||||
header: "Wilayah",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className="normal-case">{row.getValue("name")}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "mediaTypesString",
|
|
||||||
header: "Jumlah Tracking Berita Harian",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className="normal-case">{row.getValue("mediaTypesString")}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
accessorKey: "action",
|
|
||||||
header: t("action", { defaultValue: "Action" }),
|
|
||||||
enableHiding: false,
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
|
|
||||||
async function doDelete(id: any) {
|
|
||||||
// loading();
|
|
||||||
const data = {
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await deleteMedia(data);
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
error(response.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
|
|
||||||
function success() {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Sukses",
|
|
||||||
icon: "success",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "OK",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteMedia = (id: any) => {
|
|
||||||
MySwal.fire({
|
|
||||||
title: "Hapus Data",
|
|
||||||
text: "",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
cancelButtonColor: "#3085d6",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
confirmButtonText: "Hapus",
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
doDelete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
|
||||||
<Link
|
|
||||||
href={`/admin/settings/setting-tracking/detail/${row.original.id}`}
|
|
||||||
>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href={`/admin/settings/setting-tracking/update/${row.original.id}`}
|
|
||||||
>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
|
||||||
Edit
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleDeleteMedia(row.original.id)}
|
|
||||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useTableColumns;
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
// "use client";
|
|
||||||
|
|
||||||
// import { Button } from "@/components/ui/button";
|
|
||||||
// import {
|
|
||||||
// Dialog,
|
|
||||||
// DialogContent,
|
|
||||||
// DialogFooter,
|
|
||||||
// DialogHeader,
|
|
||||||
// DialogTitle,
|
|
||||||
// DialogTrigger,
|
|
||||||
// } from "@/components/ui/dialog";
|
|
||||||
// import { z } from "zod";
|
|
||||||
// import { useForm } from "react-hook-form";
|
|
||||||
// import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
// import {
|
|
||||||
// Form,
|
|
||||||
// FormControl,
|
|
||||||
// FormDescription,
|
|
||||||
// FormField,
|
|
||||||
// FormItem,
|
|
||||||
// FormLabel,
|
|
||||||
// FormMessage,
|
|
||||||
// } from "@/components/ui/form";
|
|
||||||
// import { useRouter } from "@/i18n/routing";
|
|
||||||
// import { Input } from "@/components/ui/input";
|
|
||||||
// import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
// import { getUserRoles, postCategory } from "@/service/settings/settings";
|
|
||||||
// import { Fragment, useEffect, useState } from "react";
|
|
||||||
// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
// import { Icon } from "@iconify/react/dist/iconify.js";
|
|
||||||
// import { Textarea } from "@/components/ui/textarea";
|
|
||||||
// import { close, error, loading } from "@/config/swal";
|
|
||||||
// import { useToast } from "@/components/ui/use-toast";
|
|
||||||
// import { stringify } from "querystring";
|
|
||||||
// import { useDropzone } from "react-dropzone";
|
|
||||||
// import { CloudUpload } from "lucide-react";
|
|
||||||
// import Image from "next/image";
|
|
||||||
// import { Upload } from "tus-js-client";
|
|
||||||
// import { getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
// import Cookies from "js-cookie";
|
|
||||||
// import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
// const wilayahList = [
|
|
||||||
// { id: "mabes", label: "Mabes" },
|
|
||||||
// { id: "polda", label: "Polda" },
|
|
||||||
// { id: "satker", label: "Satker" },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// const jumlahList = [5, 10, 15, 20, 25, 30];
|
|
||||||
|
|
||||||
// export default function CreateSettingTracking() {
|
|
||||||
// const t = useTranslations("Menu");
|
|
||||||
// const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
// const form = useForm({
|
|
||||||
// defaultValues: {
|
|
||||||
// wilayah: [] as string[],
|
|
||||||
// jumlah: [] as number[],
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const onSubmit = (values: any) => {
|
|
||||||
// console.log("Submitted values:", values);
|
|
||||||
// setIsOpen(false);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
// <DialogTrigger asChild>
|
|
||||||
// <Button onClick={() => setIsOpen(true)}>Tambah Setting Tracking</Button>
|
|
||||||
// </DialogTrigger>
|
|
||||||
|
|
||||||
// <DialogContent className="sm:max-w-md">
|
|
||||||
// <DialogHeader>
|
|
||||||
// <DialogTitle>Add Setting Tracking Berita Harian</DialogTitle>
|
|
||||||
// </DialogHeader>
|
|
||||||
|
|
||||||
// <Form {...form}>
|
|
||||||
// <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
||||||
// {/* Wilayah */}
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="wilayah"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Wilayah</FormLabel>
|
|
||||||
// <div className="flex gap-4">
|
|
||||||
// {wilayahList.map((item) => (
|
|
||||||
// <div key={item.id} className="flex items-center gap-2">
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value.includes(item.id)}
|
|
||||||
// onCheckedChange={(checked) => {
|
|
||||||
// const updated = checked
|
|
||||||
// ? [...field.value, item.id]
|
|
||||||
// : field.value.filter((val) => val !== item.id);
|
|
||||||
// field.onChange(updated);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// <label className="text-sm">{item.label}</label>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <FormField
|
|
||||||
// control={form.control}
|
|
||||||
// name="jumlah"
|
|
||||||
// render={({ field }) => (
|
|
||||||
// <FormItem>
|
|
||||||
// <FormLabel>Jumlah Tracking Berita Harian</FormLabel>
|
|
||||||
// <div className="flex gap-4 flex-wrap">
|
|
||||||
// {jumlahList.map((num) => (
|
|
||||||
// <div key={num} className="flex items-center gap-2">
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value.includes(num)}
|
|
||||||
// onCheckedChange={(checked) => {
|
|
||||||
// const updated = checked
|
|
||||||
// ? [...field.value, num]
|
|
||||||
// : field.value.filter((val) => val !== num);
|
|
||||||
// field.onChange(updated);
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
// <label className="text-sm">{num}</label>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </FormItem>
|
|
||||||
// )}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <DialogFooter>
|
|
||||||
// <Button type="submit">Tambah Setting</Button>
|
|
||||||
// </DialogFooter>
|
|
||||||
// </form>
|
|
||||||
// </Form>
|
|
||||||
// </DialogContent>
|
|
||||||
// </Dialog>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { getUserRoles, postCategory } from "@/service/settings/settings";
|
|
||||||
import { Fragment, useEffect, useState } from "react";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { close, error, loading } from "@/config/swal";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { stringify } from "querystring";
|
|
||||||
import { useDropzone } from "react-dropzone";
|
|
||||||
import { CloudUpload, EyeIcon, SquarePen } from "lucide-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { Upload } from "tus-js-client";
|
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
import Cookies from "js-cookie";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
const wilayahList = [
|
|
||||||
{ id: "mabes", label: "Mabes" },
|
|
||||||
{ id: "polda", label: "Polda" },
|
|
||||||
{ id: "satker", label: "Satker" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const jumlahList = [5, 10, 15, 20, 25, 30];
|
|
||||||
|
|
||||||
export default function DetailSettingTracking(props: {
|
|
||||||
id: string;
|
|
||||||
isDetail?: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, isDetail } = props;
|
|
||||||
const t = useTranslations("Menu");
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
wilayah: [] as string[],
|
|
||||||
jumlah: [] as number[],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (values: any) => {
|
|
||||||
console.log("Submitted values:", values);
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<a
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
className="flex flex-row items-center border-b p-2 hover:cursor-pointer hover:bg-black hover:text-white"
|
|
||||||
>
|
|
||||||
<EyeIcon className="w-4 h-4 me-1.5" />
|
|
||||||
Detail
|
|
||||||
</a>
|
|
||||||
</DialogTrigger>
|
|
||||||
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Add Setting Tracking Berita Harian</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
||||||
{/* Wilayah */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="wilayah"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Wilayah</FormLabel>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
{wilayahList.map((item) => (
|
|
||||||
<div key={item.id} className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value.includes(item.id)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
const updated = checked
|
|
||||||
? [...field.value, item.id]
|
|
||||||
: field.value.filter((val) => val !== item.id);
|
|
||||||
field.onChange(updated);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label className="text-sm">{item.label}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="jumlah"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Jumlah Tracking Berita Harian</FormLabel>
|
|
||||||
<div className="flex gap-4 flex-wrap">
|
|
||||||
{jumlahList.map((num) => (
|
|
||||||
<div key={num} className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value.includes(num)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
const updated = checked
|
|
||||||
? [...field.value, num]
|
|
||||||
: field.value.filter((val) => val !== num);
|
|
||||||
field.onChange(updated);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label className="text-sm">{num}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit">Tambah Setting</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,318 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
PaginationState,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { close, loading } from "@/config/swal";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import { getCategories } from "@/service/settings/settings";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import useTableColumns from "./column";
|
|
||||||
import { UploadIcon } from "lucide-react";
|
|
||||||
|
|
||||||
const AdminSettingTrackingTable = () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const t = useTranslations("Menu");
|
|
||||||
const dataChange = searchParams?.get("dataChange");
|
|
||||||
const [openModal, setOpenModal] = React.useState(false);
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
|
||||||
const [region, setRegion] = React.useState<any>();
|
|
||||||
const [regionFilter, setRegionFilter] = React.useState<string[]>([]);
|
|
||||||
const [statusFilter, setStatusFilter] = React.useState<string[]>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({});
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
|
||||||
const columns = useTableColumns();
|
|
||||||
const table = useReactTable({
|
|
||||||
data: dataTable,
|
|
||||||
columns,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const manualRegions = [
|
|
||||||
{ id: "semua", label: "Semua" },
|
|
||||||
{ id: "mabes", label: "Mabes" },
|
|
||||||
{ id: "polda", label: "Polda" },
|
|
||||||
{ id: "satker", label: "Satker" },
|
|
||||||
{ id: "internasional", label: "Internasional" },
|
|
||||||
];
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (dataChange) {
|
|
||||||
router.push("/admin/settings/category");
|
|
||||||
}
|
|
||||||
fetchData();
|
|
||||||
}, [dataChange]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const pageFromUrl = searchParams?.get("page");
|
|
||||||
if (pageFromUrl) {
|
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [page, regionFilter]);
|
|
||||||
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
loading();
|
|
||||||
|
|
||||||
const regionQuery = regionFilter.length
|
|
||||||
? `&publishedLocation=${regionFilter.join(",")}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const statusQuery = statusFilter.length
|
|
||||||
? `&isPublish=${statusFilter.join(",")}`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const response = await getCategories(
|
|
||||||
page - 1,
|
|
||||||
`${regionQuery}${statusQuery}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = response?.data?.data;
|
|
||||||
const contentData = data?.content;
|
|
||||||
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
|
||||||
item.no = (page - 1) * 10 + index + 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setTotalPage(data?.totalPages);
|
|
||||||
close();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching categories:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (type: string, id: string, checked: boolean) => {
|
|
||||||
if (type === "region") {
|
|
||||||
if (id === "semua") {
|
|
||||||
if (checked) {
|
|
||||||
// Pilih semua (kecuali 'semua' itu sendiri)
|
|
||||||
setRegionFilter(["mabes", "polda", "satker", "internasional"]);
|
|
||||||
} else {
|
|
||||||
setRegionFilter([]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let updated = checked
|
|
||||||
? [...regionFilter, id]
|
|
||||||
: regionFilter.filter((val) => val !== id);
|
|
||||||
|
|
||||||
// Jika semua sudah tercentang, maka otomatis centang "semua"
|
|
||||||
const allIds = ["mabes", "polda", "satker", "internasional"];
|
|
||||||
const allSelected = allIds.every((val) => updated.includes(val));
|
|
||||||
|
|
||||||
setRegionFilter(allSelected ? allIds : updated);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (checked) {
|
|
||||||
setStatusFilter([...statusFilter, id]);
|
|
||||||
} else {
|
|
||||||
setStatusFilter(statusFilter.filter((val: any) => val !== id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
|
||||||
<div className="flex items-end justify-between">
|
|
||||||
{/* <CreateSettingTracking /> */}
|
|
||||||
<div className="flex-none">
|
|
||||||
<Link href={"/admin/settings/setting-tracking/create"}>
|
|
||||||
<Button color="primary" className="text-white" size="md">
|
|
||||||
<UploadIcon size={18} className="mr-2" />
|
|
||||||
Tambah Setting Tracking
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button size="md" variant="outline">
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80 ">
|
|
||||||
<div className="flex flex-col gap-2 px-2">
|
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<p>Filter</p>
|
|
||||||
<a
|
|
||||||
onClick={() => fetchData()}
|
|
||||||
className="cursor-pointer text-primary"
|
|
||||||
>
|
|
||||||
Simpan
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
|
||||||
<p>Wilayah</p>
|
|
||||||
{manualRegions.map((region) => (
|
|
||||||
<div key={region.id} className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={region.id}
|
|
||||||
checked={
|
|
||||||
region.id === "semua"
|
|
||||||
? ["mabes", "polda", "satker", "internasional"].every(
|
|
||||||
(val) => regionFilter.includes(val)
|
|
||||||
)
|
|
||||||
: regionFilter.includes(region.id)
|
|
||||||
}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("region", region.id, Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor={region.id}
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{region.label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<p className="mt-3">Status</p>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="aktif"
|
|
||||||
checked={statusFilter.includes("true")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", "true", Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="aktif"
|
|
||||||
className="text-xs font-medium leading-none"
|
|
||||||
>
|
|
||||||
Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="nonaktif"
|
|
||||||
checked={statusFilter.includes("false")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleChange("status", "false", Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="nonaktif"
|
|
||||||
className="text-xs font-medium leading-none"
|
|
||||||
>
|
|
||||||
Non Aktif
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<Table className="overflow-hidden">
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
|
||||||
{headerGroup.headers.map((header) => (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
table.getRowModel().rows.map((row) => (
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
className="h-[75px]"
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
table={table}
|
|
||||||
totalData={totalData}
|
|
||||||
totalPage={totalPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminSettingTrackingTable;
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
|
||||||
import { getUserRoles, postCategory } from "@/service/settings/settings";
|
|
||||||
import { Fragment, useEffect, useState } from "react";
|
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { close, error, loading } from "@/config/swal";
|
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
|
||||||
import { stringify } from "querystring";
|
|
||||||
import { useDropzone } from "react-dropzone";
|
|
||||||
import { CloudUpload, SquareEqual, SquarePen } from "lucide-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { Upload } from "tus-js-client";
|
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
import Cookies from "js-cookie";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
const wilayahList = [
|
|
||||||
{ id: "mabes", label: "Mabes" },
|
|
||||||
{ id: "polda", label: "Polda" },
|
|
||||||
{ id: "satker", label: "Satker" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const jumlahList = [5, 10, 15, 20, 25, 30];
|
|
||||||
|
|
||||||
export default function UpdateSettingTracking(props: {
|
|
||||||
id: string;
|
|
||||||
isUpdate?: boolean;
|
|
||||||
}) {
|
|
||||||
const { id, isUpdate } = props;
|
|
||||||
const t = useTranslations("Menu");
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
wilayah: [] as string[],
|
|
||||||
jumlah: [] as number[],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (values: any) => {
|
|
||||||
console.log("Submitted values:", values);
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<a
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
className="flex flex-row items-center border-b p-2 hover:cursor-pointer hover:bg-black hover:text-white"
|
|
||||||
>
|
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
</DialogTrigger>
|
|
||||||
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Add Setting Tracking Berita Harian</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
||||||
{/* Wilayah */}
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="wilayah"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Wilayah</FormLabel>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
{wilayahList.map((item) => (
|
|
||||||
<div key={item.id} className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value.includes(item.id)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
const updated = checked
|
|
||||||
? [...field.value, item.id]
|
|
||||||
: field.value.filter((val) => val !== item.id);
|
|
||||||
field.onChange(updated);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label className="text-sm">{item.label}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="jumlah"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Jumlah Tracking Berita Harian</FormLabel>
|
|
||||||
<div className="flex gap-4 flex-wrap">
|
|
||||||
{jumlahList.map((num) => (
|
|
||||||
<div key={num} className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value.includes(num)}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
const updated = checked
|
|
||||||
? [...field.value, num]
|
|
||||||
: field.value.filter((val) => val !== num);
|
|
||||||
field.onChange(updated);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label className="text-sm">{num}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit">Tambah Setting</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import CreateSettingTracking from "@/components/form/media-tracking/setting-tracking-form";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
|
|
||||||
const SettingTrackingCreatePage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<CreateSettingTracking />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SettingTrackingCreatePage;
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import DetailSettingTracking from "@/components/form/media-tracking/setting-tracking-detail-form";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
|
|
||||||
const SettingTrackingDetailPage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<DetailSettingTracking />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SettingTrackingDetailPage;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
"use client";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
import AdminSettingTrackingTable from "./component/table";
|
|
||||||
|
|
||||||
export default function AdminSettingTracking() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<AdminSettingTrackingTable />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import UpdateSettingTracking from "@/components/form/media-tracking/setting-tracking-update-form";
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
|
||||||
|
|
||||||
const SettingTrackingUpdatePage = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteBreadcrumb />
|
|
||||||
<div className="space-y-4">
|
|
||||||
<UpdateSettingTracking />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SettingTrackingUpdatePage;
|
|
||||||
|
|
@ -109,12 +109,12 @@ export default function CreateTagModal() {
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button color="primary" size="md">
|
<Button color="primary" size="md">
|
||||||
{t("add-tags", { defaultValue: "Add Tags" })}
|
{t("add-tags")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle> {t("add-tags", { defaultValue: "Add Tags" })}</DialogTitle>
|
<DialogTitle> {t("add-tags")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ const AdminTagTable = () => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">{t("tags", { defaultValue: "Tags" })}</p>
|
<p className="text-xl font-medium text-default-900">{t("tags")}</p>
|
||||||
<CreateFAQModal />
|
<CreateFAQModal />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,22 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./column";
|
import columns from "./column";
|
||||||
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
getMediaBlastCampaignPage,
|
||||||
|
listDataMedia,
|
||||||
|
} from "@/service/broadcast/broadcast";
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { NewCampaignIcon } from "@/components/icon";
|
||||||
|
import search from "../../../app/chat/components/search";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -64,6 +79,7 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
BarChart,
|
BarChart,
|
||||||
|
|
@ -190,7 +206,7 @@ const SurveyListTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
|
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
|
||||||
<div className="flex flex-row gap-2 items-center justify-between">
|
<div className="flex flex-row gap-2 items-center justify-between">
|
||||||
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
|
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import FullCalendar from "@fullcalendar/react"; // must go before plugins
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
|
import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
|
||||||
|
import listPlugin from "@fullcalendar/list";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import ExternalDraggingevent from "./dragging-events";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { CalendarEvent, CalendarCategory } from "./data"
|
||||||
|
import {
|
||||||
|
EventContentArg,
|
||||||
|
} from '@fullcalendar/core'
|
||||||
|
import EventModal from "./event-modal";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
interface CalendarViewProps {
|
||||||
|
events: CalendarEvent[];
|
||||||
|
categories: CalendarCategory[];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(null);
|
||||||
|
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||||
|
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(null);
|
||||||
|
const [draggableInitialized, setDraggableInitialized] = useState<boolean>(false);
|
||||||
|
const t = useTranslations("CalendarApp")
|
||||||
|
// event canvas state
|
||||||
|
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
|
||||||
|
const [date, setDate] = React.useState<Date>(new Date());
|
||||||
|
|
||||||
|
const [dragEvents] = useState([
|
||||||
|
{ title: "New Event Planning", id: "101", tag: "business" },
|
||||||
|
{ title: "Meeting", id: "102", tag: "meeting" },
|
||||||
|
{ title: "Generating Reports", id: "103", tag: "holiday" },
|
||||||
|
{ title: "Create New theme", id: "104", tag: "etc" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedCategory(categories?.map((c) => c.value));
|
||||||
|
}, [events, categories]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const draggableEl = document.getElementById("external-events");
|
||||||
|
|
||||||
|
const initDraggable = () => {
|
||||||
|
if (draggableEl) {
|
||||||
|
new Draggable(draggableEl, {
|
||||||
|
itemSelector: ".fc-event",
|
||||||
|
eventData: function (eventEl) {
|
||||||
|
let title = eventEl.getAttribute("title");
|
||||||
|
let id = eventEl.getAttribute("data");
|
||||||
|
let event = dragEvents.find((e) => e.id === id);
|
||||||
|
let tag = event ? event.tag : "";
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
id: id,
|
||||||
|
extendedProps: {
|
||||||
|
calendar: tag,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dragEvents.length > 0) {
|
||||||
|
initDraggable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
draggableEl?.removeEventListener("mousedown", initDraggable);
|
||||||
|
};
|
||||||
|
}, [dragEvents]);
|
||||||
|
// event click
|
||||||
|
const handleEventClick = (arg: any) => {
|
||||||
|
setSelectedEventDate(null);
|
||||||
|
setSheetOpen(true);
|
||||||
|
setSelectedEvent(arg);
|
||||||
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
|
};
|
||||||
|
// handle close modal
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setSheetOpen(false);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
setSelectedEventDate(null);
|
||||||
|
};
|
||||||
|
const handleDateClick = (arg: any) => {
|
||||||
|
setSheetOpen(true);
|
||||||
|
setSelectedEventDate(arg);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategorySelection = (category: string) => {
|
||||||
|
if (selectedCategory && selectedCategory.includes(category)) {
|
||||||
|
setSelectedCategory(selectedCategory.filter((c) => c !== category));
|
||||||
|
} else {
|
||||||
|
setSelectedCategory([...selectedCategory || [], category]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClassName = (arg: EventContentArg) => {
|
||||||
|
|
||||||
|
if (arg.event.extendedProps.calendar === "holiday") {
|
||||||
|
return "destructive";
|
||||||
|
}
|
||||||
|
else if (arg.event.extendedProps.calendar === "business") {
|
||||||
|
return "primary";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "personal") {
|
||||||
|
return "success";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "family") {
|
||||||
|
return "info";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "etc") {
|
||||||
|
return "info";
|
||||||
|
} else if (arg.event.extendedProps.calendar === "meeting") {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredEvents = events?.filter((event) =>
|
||||||
|
selectedCategory?.includes(event.extendedProps.calendar)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
||||||
|
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<CardHeader className="border-none mb-2 pt-5">
|
||||||
|
<Button
|
||||||
|
onClick={handleDateClick}
|
||||||
|
className="dark:bg-background dark:text-foreground"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 me-1" />
|
||||||
|
{t("addEvent")}
|
||||||
|
</Button>
|
||||||
|
</CardHeader>
|
||||||
|
<div className="px-3">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
onSelect={(s) => {
|
||||||
|
handleDateClick(s);
|
||||||
|
}}
|
||||||
|
className="rounded-md border w-full p-0 border-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="external-events" className=" space-y-1.5 mt-6 px-4">
|
||||||
|
<p className="text-sm font-medium text-default-700 mb-3">
|
||||||
|
{t("shortDesc")}
|
||||||
|
</p>
|
||||||
|
{dragEvents.map((event) => (
|
||||||
|
<ExternalDraggingevent key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="py-4 text-default-800 font-semibold text-xs uppercase mt-4 mb-2 px-4">
|
||||||
|
{t("filter")}
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-3 px-4">
|
||||||
|
<li className=" flex gap-3">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedCategory?.length === categories?.length}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedCategory?.length === categories?.length) {
|
||||||
|
setSelectedCategory([]);
|
||||||
|
} else {
|
||||||
|
setSelectedCategory(categories.map((c) => c.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label>All</Label>
|
||||||
|
</li>
|
||||||
|
{categories?.map((category) => (
|
||||||
|
<li className="flex gap-3 " key={category.value}>
|
||||||
|
<Checkbox
|
||||||
|
className={category.className}
|
||||||
|
id={category.label}
|
||||||
|
checked={selectedCategory?.includes(category.value)}
|
||||||
|
onClick={() => handleCategorySelection(category.value)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={category.label}>{category.label}</Label>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
||||||
|
<CardContent className="dashcode-app-calendar">
|
||||||
|
<FullCalendar
|
||||||
|
plugins={[
|
||||||
|
dayGridPlugin,
|
||||||
|
timeGridPlugin,
|
||||||
|
interactionPlugin,
|
||||||
|
listPlugin,
|
||||||
|
]}
|
||||||
|
headerToolbar={{
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
||||||
|
}}
|
||||||
|
events={filteredEvents}
|
||||||
|
editable={true}
|
||||||
|
rerenderDelay={10}
|
||||||
|
eventDurationEditable={false}
|
||||||
|
selectable={true}
|
||||||
|
selectMirror={true}
|
||||||
|
droppable={true}
|
||||||
|
dayMaxEvents={2}
|
||||||
|
weekends={true}
|
||||||
|
eventClassNames={handleClassName}
|
||||||
|
dateClick={handleDateClick}
|
||||||
|
eventClick={handleEventClick}
|
||||||
|
initialView="dayGridMonth"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<EventModal
|
||||||
|
open={sheetOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
categories={categories}
|
||||||
|
event={selectedEvent}
|
||||||
|
selectedDate={selectedEventDate}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CalendarView;
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
const prevDay = new Date().getDate() - 1;
|
||||||
|
const nextDay = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const nextMonth = date.getMonth() === 11 ? new Date(date.getFullYear() + 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() + 1, 1)
|
||||||
|
// prettier-ignore
|
||||||
|
const prevMonth = date.getMonth() === 11 ? new Date(date.getFullYear() - 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() - 1, 1)
|
||||||
|
export const calendarEvents = [
|
||||||
|
{
|
||||||
|
id: faker.string.uuid() ,
|
||||||
|
title: "All Day Event",
|
||||||
|
start: date,
|
||||||
|
end: nextDay,
|
||||||
|
allDay: false,
|
||||||
|
//className: "warning",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Meeting With Client",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -11),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -10),
|
||||||
|
allDay: true,
|
||||||
|
//className: "success",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "personal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Lunch",
|
||||||
|
allDay: true,
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -9),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -7),
|
||||||
|
// className: "info",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "family",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Birthday Party",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -11),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -10),
|
||||||
|
allDay: true,
|
||||||
|
//className: "primary",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "meeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Birthday Party",
|
||||||
|
start: new Date(date.getFullYear(), date.getMonth() + 1, -13),
|
||||||
|
end: new Date(date.getFullYear(), date.getMonth() + 1, -12),
|
||||||
|
allDay: true,
|
||||||
|
// className: "danger",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "holiday",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: faker.string.uuid(),
|
||||||
|
title: "Monthly Meeting",
|
||||||
|
start: nextMonth,
|
||||||
|
end: nextMonth,
|
||||||
|
allDay: true,
|
||||||
|
//className: "primary",
|
||||||
|
extendedProps: {
|
||||||
|
calendar: "business",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const calendarCategories = [
|
||||||
|
{
|
||||||
|
label: "Business",
|
||||||
|
value: "business",
|
||||||
|
activeClass: "ring-primary-500 bg-primary-500",
|
||||||
|
className: "group-hover:border-blue-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Personal",
|
||||||
|
value: "personal",
|
||||||
|
activeClass: "ring-success-500 bg-success-500",
|
||||||
|
className: " group-hover:border-green-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Holiday",
|
||||||
|
value: "holiday",
|
||||||
|
activeClass: "ring-danger-500 bg-danger-500",
|
||||||
|
className: " group-hover:border-red-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Family",
|
||||||
|
value: "family",
|
||||||
|
activeClass: "ring-info-500 bg-info-500",
|
||||||
|
className: " group-hover:border-cyan-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Meeting",
|
||||||
|
value: "meeting",
|
||||||
|
activeClass: "ring-warning-500 bg-warning-500",
|
||||||
|
className: " group-hover:border-yellow-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Etc",
|
||||||
|
value: "etc",
|
||||||
|
activeClass: "ring-info-500 bg-info-500",
|
||||||
|
className: " group-hover:border-cyan-500",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const categories = [
|
||||||
|
{
|
||||||
|
label: "Business",
|
||||||
|
value: "business",
|
||||||
|
className: "data-[state=checked]:bg-primary data-[state=checked]:ring-primary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Personal",
|
||||||
|
value: "personal",
|
||||||
|
|
||||||
|
className: "data-[state=checked]:bg-success data-[state=checked]:ring-success",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Holiday",
|
||||||
|
value: "holiday",
|
||||||
|
className: "data-[state=checked]:bg-destructive data-[state=checked]:ring-destructive ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Family",
|
||||||
|
value: "family",
|
||||||
|
className: "data-[state=checked]:bg-info data-[state=checked]:ring-info ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Meeting",
|
||||||
|
value: "meeting",
|
||||||
|
className: "data-[state=checked]:bg-warning data-[state=checked]:ring-warning",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Etc",
|
||||||
|
value: "etc",
|
||||||
|
className: "data-[state=checked]:bg-info data-[state=checked]:ring-info",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type CalendarEvent = (typeof calendarEvents)[number]
|
||||||
|
export type CalendarCategory = (typeof calendarCategories)[number]
|
||||||
|
export type Category = (typeof categories)[number]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
const ExternalDraggingevent = ({ event }: any) => {
|
||||||
|
const { title, id, tag } = event;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
title={title}
|
||||||
|
data-id={id}
|
||||||
|
className="fc-event px-4 py-1.5 bg-default-100 dark:bg-default-300 rounded text-sm flex items-center gap-2 shadow-sm cursor-move" >
|
||||||
|
<span
|
||||||
|
className={cn("h-2 w-2 rounded-full block", {
|
||||||
|
"bg-primary": tag === "business",
|
||||||
|
"bg-warning": tag === "meeting",
|
||||||
|
"bg-destructive": tag === "holiday",
|
||||||
|
"bg-info": tag === "etc",
|
||||||
|
})}
|
||||||
|
></span>
|
||||||
|
<span className="text-sm font-medium text-default-900">{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExternalDraggingevent;
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
"use client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Loader2, CalendarIcon } from "lucide-react";
|
||||||
|
import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog";
|
||||||
|
import { CalendarCategory } from "./data";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
title: z.string().min(3, { message: "Required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const EventModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
categories,
|
||||||
|
event,
|
||||||
|
selectedDate,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
categories: any;
|
||||||
|
event: any;
|
||||||
|
selectedDate: any;
|
||||||
|
}) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||||
|
const [isPending, startTransition] = React.useTransition();
|
||||||
|
const [calendarProps, setCalendarProps] = React.useState<any>(
|
||||||
|
categories[0].value
|
||||||
|
);
|
||||||
|
// delete modal state
|
||||||
|
const [deleteModalOpen, setDeleteModalOpen] = useState<boolean>(false);
|
||||||
|
const [eventIdToDelete, setEventIdToDelete] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data: any) => {
|
||||||
|
startTransition(() => {
|
||||||
|
if (!event) {
|
||||||
|
data.start = startDate;
|
||||||
|
data.end = endDate;
|
||||||
|
data.allDay = false;
|
||||||
|
data.extendedProps = {
|
||||||
|
calendar: calendarProps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedDate) {
|
||||||
|
setStartDate(selectedDate.date);
|
||||||
|
setEndDate(selectedDate.date);
|
||||||
|
}
|
||||||
|
if (event) {
|
||||||
|
setStartDate(event?.event?.start);
|
||||||
|
setEndDate(event?.event?.end);
|
||||||
|
const eventCalendar = event?.event?.extendedProps?.calendar;
|
||||||
|
if (eventCalendar) {
|
||||||
|
setCalendarProps(eventCalendar);
|
||||||
|
} else {
|
||||||
|
setCalendarProps(categories[0].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValue("title", event?.event?.title || "");
|
||||||
|
}, [event, selectedDate, open, categories, setValue]);
|
||||||
|
|
||||||
|
const onDeleteEventAction = async () => {
|
||||||
|
try {
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenDeleteModal = (eventId: string) => {
|
||||||
|
setEventIdToDelete(eventId);
|
||||||
|
setDeleteModalOpen(true);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirmationDialog
|
||||||
|
open={deleteModalOpen}
|
||||||
|
onClose={() => setDeleteModalOpen(false)}
|
||||||
|
onConfirm={onDeleteEventAction}
|
||||||
|
defaultToast={false}
|
||||||
|
/>
|
||||||
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
|
<DialogContent onPointerDownOutside={onClose}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{event ? "Edit Event" : "Create Event"} {event?.title}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="mt-6 h-full">
|
||||||
|
<form className="h-full" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="space-y-4 pb-5 ">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="title">Event Name</Label>
|
||||||
|
<Input
|
||||||
|
id="title"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter Event Name"
|
||||||
|
{...register("title")}
|
||||||
|
/>
|
||||||
|
{errors?.title?.message && (
|
||||||
|
<div className="text-destructive text-sm">
|
||||||
|
{typeof errors?.title?.message === "string"
|
||||||
|
? errors?.title?.message
|
||||||
|
: JSON.stringify(errors?.title?.message)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="startDate">Start Date </Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between text-left font-normal border-default-200 text-default-600 md:px-4",
|
||||||
|
!startDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{startDate ? (
|
||||||
|
format(startDate, "PP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="startDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={startDate}
|
||||||
|
onSelect={(date) => setStartDate(date as Date)}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="endDate">End Date</Label>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
className={cn(
|
||||||
|
"w-full justify-between text-left font-normal border-default-200 text-default-600 md:px-4",
|
||||||
|
!endDate && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{endDate ? (
|
||||||
|
format(endDate, "PP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0">
|
||||||
|
<Controller
|
||||||
|
name="endDate"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={(date) => setEndDate(date as Date)}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="calendarProps">Label </Label>
|
||||||
|
<Controller
|
||||||
|
name="calendarProps"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
value={calendarProps}
|
||||||
|
onValueChange={(data) => setCalendarProps(data)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Label" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((category: CalendarCategory) => (
|
||||||
|
<SelectItem
|
||||||
|
value={category.value}
|
||||||
|
key={category.value}
|
||||||
|
>
|
||||||
|
{category.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2 mt-10">
|
||||||
|
<Button type="submit" disabled={isPending} className="flex-1">
|
||||||
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="me-2 h-4 w-4 animate-spin" />
|
||||||
|
{event ? "Updating..." : "Adding..."}
|
||||||
|
</>
|
||||||
|
) : event ? (
|
||||||
|
"Update Event"
|
||||||
|
) : (
|
||||||
|
"Add Event"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{event && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="destructive"
|
||||||
|
onClick={() => handleOpenDeleteModal(event?.event?.id)}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventModal;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "Ticketing",
|
title: "Calender",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue