trafficmonitor init...2
This commit is contained in:
parent
58680bacb2
commit
e9f9902761
@ -16,7 +16,7 @@ $routes->group('cli', ['namespace' => 'App\Controllers\CLI'], function ($routes)
|
||||
$routes->cli('collect', 'Collector::execute');
|
||||
});
|
||||
$routes->group('', ['namespace' => 'App\Controllers'], function ($routes) {
|
||||
$routes->get('/', 'Home::index');
|
||||
$routes->get('/', 'Home::welcome');
|
||||
$routes->group('auth', ['namespace' => 'App\Controllers\Auth'], function ($routes) {
|
||||
$routes->get('login', 'LocalController::login_form');
|
||||
$routes->post('login', 'LocalController::login');
|
||||
@ -26,7 +26,7 @@ $routes->group('', ['namespace' => 'App\Controllers'], function ($routes) {
|
||||
});
|
||||
//Admin 관련
|
||||
$routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'authFilter:manager'], function ($routes) {
|
||||
$routes->get('/', 'Home::index');
|
||||
$routes->get('/', 'Home::welcome');
|
||||
$routes->group('user', ['namespace' => 'App\Controllers\Admin'], function ($routes) {
|
||||
$routes->get('/', 'UserController::index');
|
||||
$routes->get('create', 'UserController::create_form');
|
||||
|
||||
170
app/Controllers/AbstractCrudController.php
Normal file
170
app/Controllers/AbstractCrudController.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\CommonEntity;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
|
||||
/**
|
||||
* AbstractCrudController
|
||||
* 단일 레코드 생성(C), 조회(R), 수정(U), 삭제(D) 로직을 담당합니다. (SRP: Single Record Management)
|
||||
*/
|
||||
abstract class AbstractCrudController extends AbstractWebController
|
||||
{
|
||||
// --- 생성 (Create) ---
|
||||
|
||||
protected function create_form_process(array $formDatas = []): array
|
||||
{
|
||||
// Form Default값 설정 (오버라이드 포인트)
|
||||
return $formDatas;
|
||||
}
|
||||
|
||||
protected function create_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function create_form(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('formDatas', $this->create_form_process());
|
||||
return $this->create_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function create_process(): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.)
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->create($dto);
|
||||
}
|
||||
|
||||
protected function create_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function create(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->create_result_process($this->create_process());
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- 수정 (Modify) ---
|
||||
|
||||
protected function modify_form_process($uid): CommonEntity
|
||||
{
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function modify_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function modify_form($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->modify_form_process($uid));
|
||||
return $this->modify_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function modify_process($uid): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->modify($uid, $dto);
|
||||
}
|
||||
|
||||
protected function modify_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function modify($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->modify_result_process($this->modify_process($uid));
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- 삭제 (Delete) ---
|
||||
|
||||
protected function delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
protected function delete_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function delete($uid): RedirectResponse
|
||||
{
|
||||
try {
|
||||
return $this->delete_result_process($this->delete_process($uid));
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- 상세보기 (View) ---
|
||||
|
||||
protected function view_process($uid): CommonEntity
|
||||
{
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function view_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function view($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->view_process($uid));
|
||||
return $this->view_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
162
app/Controllers/AbstractWebController.php
Normal file
162
app/Controllers/AbstractWebController.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\AuthContext;
|
||||
use App\Traits\LogTrait;
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* AbstractWebController
|
||||
* 모든 웹 컨트롤러의 최상위 추상 클래스.
|
||||
* 공통 초기화, 뷰 데이터 관리, 리다이렉션/렌더링 로직을 담당합니다. (SRP: Utility & Presentation Layer)
|
||||
*/
|
||||
abstract class AbstractWebController extends Controller
|
||||
{
|
||||
use LogTrait;
|
||||
|
||||
protected $service = null;
|
||||
private array $_action_paths = [];
|
||||
private array $_viewDatas = [];
|
||||
private ?string $_title = null;
|
||||
|
||||
// --- 초기화 및 DI ---
|
||||
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
}
|
||||
|
||||
final protected function getAuthContext(): AuthContext
|
||||
{
|
||||
return service('myauth')->getAuthContext();
|
||||
}
|
||||
|
||||
protected function getTitle(): string
|
||||
{
|
||||
if ($this->_title === null) {
|
||||
// 이 로직은 하위 클래스에서 service가 초기화되었다고 가정합니다.
|
||||
$this->_title = lang("{$this->service->getClassPaths(false)}.title");
|
||||
}
|
||||
return $this->_title;
|
||||
}
|
||||
|
||||
// --- 경로 및 뷰 데이터 관리 ---
|
||||
|
||||
final protected function addActionPaths(string $path)
|
||||
{
|
||||
$this->_action_paths[] = $path;
|
||||
}
|
||||
|
||||
final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string
|
||||
{
|
||||
return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths);
|
||||
}
|
||||
|
||||
final protected function addViewDatas(string $key, mixed $value)
|
||||
{
|
||||
$this->_viewDatas[$key] = $value;
|
||||
}
|
||||
|
||||
final protected function getViewDatas(?string $key = null): mixed
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->_viewDatas;
|
||||
}
|
||||
return $this->_viewDatas[$key] ?? null;
|
||||
}
|
||||
|
||||
// --- 공통 처리 로직 (Override 가능) ---
|
||||
|
||||
/**
|
||||
* 모든 액션 실행 전 공통 초기화 작업
|
||||
*/
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
$this->addViewDatas('action', $action);
|
||||
$this->addViewDatas('authContext', $this->getAuthContext());
|
||||
// $this->service가 하위 클래스에서 설정되었다고 가정
|
||||
$this->addViewDatas('classPath', $this->service->getClassPaths(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 액션 성공 후 모달을 닫고 부모 창을 리로드하는 스크립트를 반환합니다.
|
||||
*/
|
||||
protected function action_modal_process(string $message): string
|
||||
{
|
||||
return "
|
||||
<script>
|
||||
// IMPORTANT: The original alert() should be replaced with a custom modal/toast in production environment.
|
||||
// alert('" . addslashes($message) . "');
|
||||
|
||||
// Bootstrap 5 모달 닫기
|
||||
var modalEl = document.getElementById('modal_action_form');
|
||||
if (modalEl) {
|
||||
var modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (!modal) {
|
||||
modal = new bootstrap.Modal(modalEl);
|
||||
}
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
// 모달 닫힌 후 부모 페이지 새로고침 (애니메이션 고려)
|
||||
setTimeout(function() {
|
||||
if (window.parent) {
|
||||
window.parent.location.reload();
|
||||
} else if (window.opener && !window.opener.closed) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
}, 500);
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
/**
|
||||
* 액션 결과에 따라 지정된 메시지와 URL로 리다이렉션합니다.
|
||||
*/
|
||||
protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse
|
||||
{
|
||||
switch ($type) {
|
||||
case 'warning':
|
||||
case 'error':
|
||||
case 'critical':
|
||||
case 'alert':
|
||||
case 'emergency':
|
||||
log_message($type, $message);
|
||||
$result = $redirect_url ? redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message);
|
||||
break;
|
||||
case 'debug':
|
||||
case 'info':
|
||||
case 'notice':
|
||||
default:
|
||||
log_message($type, $message);
|
||||
$redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths());
|
||||
$result = redirect()->to($redirect_url)->with('message', $message);
|
||||
break;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 뷰 경로와 데이터를 이용하여 최종 HTML을 렌더링합니다.
|
||||
*/
|
||||
protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string
|
||||
{
|
||||
$full_path = implode(DIRECTORY_SEPARATOR, $view_paths);
|
||||
$final_path = $this->request->getVar('ActionTemplate');
|
||||
if ($final_path) {
|
||||
$full_path .= DIRECTORY_SEPARATOR . $final_path;
|
||||
}
|
||||
$view_datas = [
|
||||
...$viewDatas,
|
||||
'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []],
|
||||
];
|
||||
|
||||
helper(['form', __FUNCTION__]);
|
||||
return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]);
|
||||
}
|
||||
}
|
||||
@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface;
|
||||
|
||||
class CollectorController extends AdminController
|
||||
{
|
||||
public const PATH = 'collector';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('collectorservice');
|
||||
}
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
|
||||
@ -2,20 +2,19 @@
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Controllers\CommonController;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Home extends AdminController
|
||||
{
|
||||
public const PATH = 'welcome';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
//Index,FieldForm관련
|
||||
public function index(): string
|
||||
public function welcome(): string
|
||||
{
|
||||
$dashboards = [];
|
||||
foreach (service('trafficservice')->getEntities(['status' => STATUS['AVAILABLE']]) as $entity)
|
||||
|
||||
@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface;
|
||||
|
||||
class MylogController extends AdminController
|
||||
{
|
||||
public const PATH = 'mylog';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('mylogservice');
|
||||
}
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
|
||||
@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface;
|
||||
|
||||
class TrafficController extends AdminController
|
||||
{
|
||||
public const PATH = 'traffic';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('trafficservice');
|
||||
}
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
@ -81,7 +79,7 @@ class TrafficController extends AdminController
|
||||
}
|
||||
return $this->action_render_process(
|
||||
$this->getActionPaths(),
|
||||
self::PATH . DIRECTORY_SEPARATOR . __FUNCTION__,
|
||||
'traffic' . DIRECTORY_SEPARATOR . __FUNCTION__,
|
||||
['entity' => $entity]
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,14 +9,12 @@ use Psr\Log\LoggerInterface;
|
||||
|
||||
class UserController extends AdminController
|
||||
{
|
||||
public const PATH = 'user';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
if ($this->service === null) {
|
||||
$this->service = service('userservice');
|
||||
}
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
//Action작업관련
|
||||
protected function action_init_process(string $action): void
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Controllers\Auth;
|
||||
|
||||
use App\Controllers\CommonController;
|
||||
|
||||
use App\Controllers\AbstractWebController;
|
||||
use App\Entities\UserEntity;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
@ -11,7 +10,7 @@ use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
abstract class AuthController extends CommonController
|
||||
abstract class AuthController extends AbstractWebController
|
||||
{
|
||||
public const PATH = 'auth';
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
|
||||
@ -2,16 +2,16 @@
|
||||
|
||||
namespace App\Controllers\CLI;
|
||||
|
||||
use App\Controllers\CommonController;
|
||||
use App\Controllers\BaseController;
|
||||
use App\DTOs\CollectorDTO;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Collector extends CommonController
|
||||
class Collector extends BaseController
|
||||
{
|
||||
public const PATH = 'collector';
|
||||
private $service = null;
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
@ -25,7 +25,6 @@ class Collector extends CommonController
|
||||
$this->service->getFormService()->setFormOptions($filters);
|
||||
$this->service->getFormService()->setBatchjobFilters($filters);
|
||||
}
|
||||
$this->addActionPaths(self::PATH);
|
||||
}
|
||||
public function execute(): void
|
||||
{
|
||||
|
||||
@ -3,668 +3,352 @@
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Controllers\BaseController;
|
||||
use App\Libraries\AuthContext;
|
||||
use App\Traits\LogTrait;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use CodeIgniter\HTTP\DownloadResponse;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Html;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
abstract class CommonController extends BaseController
|
||||
/**
|
||||
* CommonController
|
||||
* 목록(index), 일괄작업(batchjob), 일괄삭제, 다운로드 로직을 담당합니다. (SRP: Collection Management)
|
||||
*/
|
||||
abstract class CommonController extends AbstractCrudController
|
||||
{
|
||||
use LogTrait;
|
||||
use ResponseTrait;
|
||||
protected $request;
|
||||
// --- 목록 (Index / List) 관련 ---
|
||||
|
||||
private array $_action_paths = [];
|
||||
private array $_viewDatas = [];
|
||||
protected $service = null;
|
||||
private ?string $_title = null;
|
||||
/**
|
||||
* 조건절(필터, 검색어, 날짜, 정렬)을 처리합니다. (Override 가능)
|
||||
*/
|
||||
protected function index_condition_process(string $action): void
|
||||
{
|
||||
// Filter조건절 처리
|
||||
$index_filters = [];
|
||||
foreach ($this->service->getFormService()->getFormFilters($action) as $field) {
|
||||
$value = $this->request->getVar($field) ?? null;
|
||||
if ($value) {
|
||||
$this->service->setFilter($field, $value);
|
||||
$index_filters[$field] = $value;
|
||||
}
|
||||
}
|
||||
$this->addViewDatas('index_filters', $index_filters);
|
||||
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
// 검색어조건절 처리
|
||||
$index_word = $this->request->getVar('index_word');
|
||||
if ($index_word !== null && $index_word !== '') {
|
||||
$this->service->setSearchWord($index_word);
|
||||
}
|
||||
$this->addViewDatas('index_word', $index_word);
|
||||
|
||||
// 날자검색
|
||||
$index_start = $this->request->getVar('index_start');
|
||||
$index_end = $this->request->getVar('index_end');
|
||||
if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') {
|
||||
$this->service->setDateFilter($index_start, $index_end);
|
||||
}
|
||||
$this->addViewDatas('index_start', $index_start);
|
||||
$this->addViewDatas('index_end', $index_end);
|
||||
|
||||
// OrderBy처리
|
||||
$order_field = $this->request->getVar('order_field');
|
||||
$order_value = $this->request->getVar('order_value');
|
||||
$this->service->setOrderBy($order_field, $order_value);
|
||||
$this->addViewDatas('order_field', $order_field);
|
||||
$this->addViewDatas('order_value', $order_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagenation Select Box 옵션을 생성합니다. (Override 가능)
|
||||
*/
|
||||
protected function pagenation_options_process(int $index_totalcount, int $perpage): array
|
||||
{
|
||||
$page_options = ["" => "줄수선택"];
|
||||
// 기존 로직 유지
|
||||
for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) {
|
||||
$page_options[$i] = $i;
|
||||
}
|
||||
$page_options[$index_totalcount] = $index_totalcount;
|
||||
return $page_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* PageNation 링크를 생성하고 뷰 데이터에 추가합니다. (Override 가능)
|
||||
*/
|
||||
protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed
|
||||
{
|
||||
$pager = service("pager");
|
||||
$pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group);
|
||||
$this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group));
|
||||
return $pager->links($pager_group, $template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Service에서 엔티티 목록을 가져와 처리합니다. (Override 가능)
|
||||
*/
|
||||
protected function index_process(array $entities = []): array
|
||||
{
|
||||
foreach ($this->service->getEntities() as $entity) {
|
||||
$entities[] = $entity;
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML View 출력을 처리합니다. (Override 가능)
|
||||
*/
|
||||
protected function index_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
/**
|
||||
* 인덱스(목록) 페이지의 메인 로직입니다.
|
||||
*/
|
||||
final public function index(): string|ResponseInterface
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
try {
|
||||
// 초기화
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('uri', $this->request->getUri());
|
||||
|
||||
// Paging 설정
|
||||
$page = (int) $this->request->getVar('page') ?: 1;
|
||||
$perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10);
|
||||
$this->addViewDatas('page', $page);
|
||||
$this->addViewDatas('perpage', $perpage);
|
||||
|
||||
// 1. Total Count 계산을 위한 조건절 처리 (오버라이드 가능)
|
||||
$this->index_condition_process($action);
|
||||
$index_totalcount = $this->service->getTotalCount();
|
||||
$this->addViewDatas('index_totalcount', $index_totalcount);
|
||||
|
||||
// Pagination 설정
|
||||
$this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage));
|
||||
$this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage));
|
||||
|
||||
// 2. 실제 리스트를 위한 조건절, LIMIT, OFFSET 처리 (오버라이드 가능)
|
||||
$this->index_condition_process($action); // 조건절을 다시 호출하여 필터/검색어 유지
|
||||
$this->service->setLimit($perpage);
|
||||
$this->service->setOffset(($page - 1) * $perpage);
|
||||
|
||||
// Entities 처리
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
helper(['form']);
|
||||
$this->addViewDatas('formDatas', $this->request->getVar() ?? []);
|
||||
} catch (\Exception $e) {
|
||||
session()->setFlashdata('message', $e->getMessage());
|
||||
}
|
||||
|
||||
final protected function getAuthContext(): AuthContext
|
||||
{
|
||||
return service('myauth')->getAuthContext();
|
||||
// 현재 URL을 이전 URL 스택에 저장
|
||||
$this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : ""));
|
||||
|
||||
// HTML 뷰 렌더링
|
||||
return $this->index_result_process($action);
|
||||
}
|
||||
|
||||
// --- 일괄 작업 (Batch Job) ---
|
||||
|
||||
protected function batchjob_pre_process(array $postDatas = []): array
|
||||
{
|
||||
$postDatas = $this->request->getPost();
|
||||
|
||||
// 1. postDatas에서 선택된 uids 정보 추출
|
||||
$uids = $postDatas['batchjob_uids'] ?? [];
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("적용할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
|
||||
final protected function addActionPaths(string $path)
|
||||
{
|
||||
$this->_action_paths[] = $path;
|
||||
// 2. 변경할 데이터 추출 및 정리
|
||||
unset($postDatas['batchjob_uids'], $postDatas['batchjob_submit']);
|
||||
|
||||
$formDatas = array_filter($postDatas, fn($value) => $value !== "" && $value !== null);
|
||||
|
||||
if (empty($formDatas)) {
|
||||
throw new \Exception("변경할 조건항목을 선택하셔야합니다.");
|
||||
}
|
||||
|
||||
final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string
|
||||
{
|
||||
return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths);
|
||||
}
|
||||
// 3. 데이터가 있는 필드 추출
|
||||
$selectedFields = array_keys($formDatas);
|
||||
return array($uids, $selectedFields, $formDatas);
|
||||
}
|
||||
|
||||
final protected function addViewDatas(string $key, mixed $value)
|
||||
{
|
||||
$this->_viewDatas[$key] = $value;
|
||||
}
|
||||
protected function batchjob_process($uid, array $formDatas): CommonEntity
|
||||
{
|
||||
// Service 로직 호출 (오버라이드 포인트)
|
||||
return $this->service->batchjob($uid, $formDatas);
|
||||
}
|
||||
|
||||
final protected function getViewDatas(?string $key = null): mixed
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->_viewDatas;
|
||||
}
|
||||
return $this->_viewDatas[$key] ?? null;
|
||||
}
|
||||
protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
protected function getTitle(): string
|
||||
{
|
||||
if ($this->_title === null) {
|
||||
$this->_title = lang("{$this->service->getClassPaths(false)}.title");
|
||||
}
|
||||
return $this->_title;
|
||||
}
|
||||
final public function batchjob(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
// 사전작업 및 데이터 추출
|
||||
list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process();
|
||||
|
||||
//공통 필수기능
|
||||
//필수함수
|
||||
//사용자정의 함수
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
$this->addViewDatas('action', $action);
|
||||
$this->addViewDatas('authContext', $this->getAuthContext());
|
||||
$this->addViewDatas('classPath', $this->service->getClassPaths(false));
|
||||
}
|
||||
// 초기화
|
||||
$this->service->getFormService()->setFormFields($selectedFields);
|
||||
$this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields);
|
||||
$this->service->getFormService()->setFormFilters($selectedFields);
|
||||
$this->service->getFormService()->setFormOptions($selectedFields);
|
||||
|
||||
protected function action_modal_process(string $message): string
|
||||
{
|
||||
return "
|
||||
<script>
|
||||
// IMPORTANT: The original alert() should be replaced with a custom modal/toast in production environment.
|
||||
// alert('" . addslashes($message) . "');
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
|
||||
// Bootstrap 5 모달 닫기
|
||||
var modalEl = document.getElementById('modal_action_form');
|
||||
if (modalEl) {
|
||||
var modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (!modal) {
|
||||
modal = new bootstrap.Modal(modalEl);
|
||||
}
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
// 모달 닫힌 후 부모 페이지 새로고침 (애니메이션 고려)
|
||||
setTimeout(function() {
|
||||
if (window.parent) {
|
||||
window.parent.location.reload();
|
||||
} else if (window.opener && !window.opener.closed) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
}, 500);
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse
|
||||
{
|
||||
switch ($type) {
|
||||
case 'warning':
|
||||
case 'error':
|
||||
case 'critical':
|
||||
case 'alert':
|
||||
case 'emergency':
|
||||
log_message($type, $message);
|
||||
$result = $redirect_url ? $result = redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message);
|
||||
break;
|
||||
case 'debug':
|
||||
case 'info':
|
||||
case 'notice':
|
||||
default:
|
||||
log_message($type, $message);
|
||||
$redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths());
|
||||
$result = redirect()->to($redirect_url)->with('message', $message);
|
||||
break;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string
|
||||
{
|
||||
$lastest_path = array_pop($view_paths); //paths는 마지막을 뺀 앞단까지만 남음
|
||||
|
||||
$full_path = implode(DIRECTORY_SEPARATOR, $view_paths);
|
||||
|
||||
// GET/POST/JSON 데이터에서 'ActionTemplate' 파라미터를 가져옵니다.
|
||||
$final_path = $this->request->getVar('ActionTemplate');
|
||||
|
||||
if ($final_path) {
|
||||
$full_path .= DIRECTORY_SEPARATOR . $final_path;
|
||||
}
|
||||
|
||||
$view_datas = [
|
||||
...$viewDatas,
|
||||
'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []],
|
||||
];
|
||||
|
||||
helper(['form', __FUNCTION__]);
|
||||
return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]);
|
||||
}
|
||||
|
||||
//생성
|
||||
protected function create_form_process(array $formDatas = []): array
|
||||
{
|
||||
//Form Default값 설정
|
||||
return $formDatas;
|
||||
}
|
||||
|
||||
protected function create_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function create_form(): string|RedirectResponse
|
||||
{
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('formDatas', $this->create_form_process());
|
||||
return $this->create_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function create_process(): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.)
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->create($dto);
|
||||
}
|
||||
|
||||
protected function create_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function create(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->create_result_process($this->create_process());
|
||||
$entities[] = $this->batchjob_process($uid, $formDatas);
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage());
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage());
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//수정
|
||||
protected function modify_form_process($uid): CommonEntity
|
||||
{
|
||||
// --- 일괄 삭제 (Batch Job Delete) ---
|
||||
|
||||
protected function batchjob_delete_pre_process(): array
|
||||
{
|
||||
$postDatas = $this->request->getPost();
|
||||
$uids = $postDatas['batchjob_uids'] ?? [];
|
||||
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("삭제할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
// $uids는 배열로 반환
|
||||
return $uids;
|
||||
}
|
||||
|
||||
protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 삭제 로직을 재사용 (Override 가능)
|
||||
*/
|
||||
protected function batchjob_delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
final public function batchjob_delete(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$uids = $this->batchjob_delete_pre_process();
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$entities[] = $this->batchjob_delete_process($uid);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_delete_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// --- 문서 다운로드 (Download) ---
|
||||
|
||||
protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array
|
||||
{
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download";
|
||||
switch ($document_type) {
|
||||
case 'excel':
|
||||
$file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = IOFactory::createWriter($loaded_data, 'Xlsx');
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
case 'pdf':
|
||||
$file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = new Mpdf($loaded_data);
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("지원하지 않는 다운로드 타입입니다: {$document_type}");
|
||||
}
|
||||
return array($full_path, $file_name);
|
||||
}
|
||||
|
||||
protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
switch ($output_type) {
|
||||
case 'excel':
|
||||
case 'pdf':
|
||||
helper(['form']);
|
||||
// 전체 목록을 다운로드하므로, 목록 조건절을 처리합니다.
|
||||
$this->index_condition_process($action);
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
|
||||
// HTML로 렌더링된 내용을 가져옵니다.
|
||||
$html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
|
||||
// HTML을 PhpSpreadsheet 객체로 로드합니다.
|
||||
$reader = new Html();
|
||||
$loaded_data = $reader->loadFromString($html);
|
||||
|
||||
// 파일 저장 및 정보 가져오기
|
||||
list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data);
|
||||
$full_path .= DIRECTORY_SEPARATOR . $file_name;
|
||||
break;
|
||||
default:
|
||||
// 개별 파일 다운로드 로직
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
if (!$entity) {
|
||||
throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
$this->addViewDatas('entity', $entity);
|
||||
list($file_name, $uploaded_filename) = $entity->getDownlaodFile();
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename;
|
||||
break;
|
||||
}
|
||||
return $this->response->download($full_path, null)->setFileName($file_name);
|
||||
}
|
||||
|
||||
protected function modify_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function modify_form($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->modify_form_process($uid));
|
||||
return $this->modify_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function modify_process($uid): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->modify($uid, $dto);
|
||||
}
|
||||
|
||||
protected function modify_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function modify($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->modify_result_process($this->modify_process($uid));
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//일괄처리
|
||||
protected function batchjob_pre_process(array $postDatas = []): array
|
||||
{
|
||||
// POST 데이터 전체를 가져옵니다.
|
||||
foreach ($this->request->getPost() as $field => $value) {
|
||||
$postDatas[$field] = $value;
|
||||
}
|
||||
//1. postDatas에서 선택된 uids 정보 추출
|
||||
$uids = [];
|
||||
if (isset($postDatas['batchjob_uids'])) {
|
||||
$uids = $postDatas['batchjob_uids'];
|
||||
unset($postDatas['batchjob_uids']);
|
||||
}
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("적용할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
//2. postDatas에서 필요없는 정보 버림
|
||||
unset($postDatas['batchjob_submit']);
|
||||
//3. 나머지는 $formDatas로 사용할 데이터 추출
|
||||
$formDatas = [];
|
||||
foreach ($postDatas as $field => $value) {
|
||||
if ($value !== "") {
|
||||
$formDatas[$field] = $value;
|
||||
}
|
||||
}
|
||||
if (empty($formDatas)) {
|
||||
throw new \Exception("변경할 조건항목을 선택하셔야합니다.");
|
||||
}
|
||||
//4. 데이터가 있는 필드 추출
|
||||
$selectedFields = array_keys($formDatas);
|
||||
return array($uids, $selectedFields, $formDatas);
|
||||
}
|
||||
|
||||
protected function batchjob_process($uid, array $formDatas): CommonEntity
|
||||
{
|
||||
return $this->service->batchjob($uid, $formDatas);
|
||||
}
|
||||
|
||||
protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
final public function batchjob(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
//사전작업
|
||||
list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process();
|
||||
//초기화
|
||||
$this->service->getFormService()->setFormFields($selectedFields);
|
||||
$this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields);
|
||||
$this->service->getFormService()->setFormFilters($selectedFields);
|
||||
$this->service->getFormService()->setFormOptions($selectedFields);
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$entities[] = $this->batchjob_process($uid, $formDatas);
|
||||
} catch (ValidationException $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//삭제
|
||||
protected function delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
protected function delete_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function delete($uid): RedirectResponse
|
||||
{
|
||||
try {
|
||||
return $this->delete_result_process($this->delete_process($uid));
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//일괄삭제
|
||||
protected function batchjob_delete_pre_process(): array
|
||||
{
|
||||
// POST 데이터 전체를 가져옵니다.
|
||||
$postDatas = $this->request->getPost();
|
||||
//1. postDatas에서 선택된 uids 정보 추출
|
||||
$uids = [];
|
||||
if (isset($postDatas['batchjob_uids'])) {
|
||||
$uids = $postDatas['batchjob_uids'];
|
||||
unset($postDatas['batchjob_uids']);
|
||||
}
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("삭제할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
return array($uids);
|
||||
}
|
||||
|
||||
protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
protected function batchjob_delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
final public function batchjob_delete(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$uids = $this->batchjob_delete_pre_process();
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$entities[] = $this->batchjob_delete_process($uid);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_delete_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//상세보기
|
||||
protected function view_process($uid): CommonEntity
|
||||
{
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function view_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function view($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->view_process($uid));
|
||||
return $this->view_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//리스트관련
|
||||
//조건절 처리
|
||||
protected function index_condition_process(string $action): void
|
||||
{
|
||||
//Filter조건절 처리
|
||||
$index_filters = [];
|
||||
foreach ($this->service->getFormService()->getFormFilters($action) as $field) {
|
||||
// getVar()를 사용하여 GET, POST, JSON 입력을 통합하여 가져옵니다.
|
||||
$value = $this->request->getVar($field) ?? null;
|
||||
if ($value) {
|
||||
$this->service->setFilter($field, $value);
|
||||
$index_filters[$field] = $value;
|
||||
}
|
||||
}
|
||||
$this->addViewDatas('index_filters', $index_filters);
|
||||
|
||||
//검색어조건절 처리
|
||||
$index_word = $this->request->getVar('index_word');
|
||||
if ($index_word !== null && $index_word !== '') {
|
||||
$this->service->setSearchWord($index_word);
|
||||
}
|
||||
$this->addViewDatas('index_word', $index_word);
|
||||
|
||||
//날자검색
|
||||
$index_start = $this->request->getVar('index_start');
|
||||
$index_end = $this->request->getVar('index_end');
|
||||
if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') {
|
||||
$this->service->setDateFilter($index_start, $index_end);
|
||||
}
|
||||
$this->addViewDatas('index_start', $index_start);
|
||||
$this->addViewDatas('index_end', $index_end);
|
||||
|
||||
//OrcerBy처리
|
||||
$order_field = $this->request->getVar('order_field');
|
||||
$order_value = $this->request->getVar('order_value');
|
||||
$this->service->setOrderBy($order_field, $order_value);
|
||||
$this->addViewDatas('order_field', $order_field);
|
||||
$this->addViewDatas('order_value', $order_value);
|
||||
}
|
||||
|
||||
//Index Option출력용
|
||||
protected function pagenation_options_process(int $index_totalcount, int $perpage): array
|
||||
{
|
||||
$page_options = ["" => "줄수선택"];
|
||||
for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) {
|
||||
$page_options[$i] = $i;
|
||||
}
|
||||
$page_options[$index_totalcount] = $index_totalcount;
|
||||
return $page_options;
|
||||
}
|
||||
|
||||
//PageNation 처리
|
||||
protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed
|
||||
{
|
||||
// 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성
|
||||
// 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full',
|
||||
// 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가
|
||||
$pager = service("pager");
|
||||
$pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group);
|
||||
// $page = $pager->getCurrentPage($pager_group);
|
||||
$this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group));
|
||||
return $pager->links($pager_group, $template);
|
||||
}
|
||||
|
||||
//Entities처리 (하위 클래스에서 오버라이드 가능)
|
||||
protected function index_process(array $entities = []): array
|
||||
{
|
||||
foreach ($this->service->getEntities() as $entity) {
|
||||
$entities[] = $entity;
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
// HTML View 출력 처리 (기존 로직)
|
||||
protected function index_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
protected function index_api_result_process(string $action): ResponseInterface
|
||||
{
|
||||
$viewDatas = $this->getViewDatas();
|
||||
|
||||
// 필요한 핵심 데이터만 추출
|
||||
$data = [
|
||||
'entities' => $viewDatas['entities'] ?? [],
|
||||
'total_count' => $viewDatas['index_totalcount'] ?? 0,
|
||||
'page' => $viewDatas['page'] ?? 1,
|
||||
'perpage' => $viewDatas['perpage'] ?? (DEFAULTS['INDEX_PERPAGE'] ?? 10), // DEFAULTS 확인 필요
|
||||
'filters' => $viewDatas['index_filters'] ?? [],
|
||||
'index_word' => $viewDatas['index_word'] ?? null,
|
||||
'index_start' => $viewDatas['index_start'] ?? null,
|
||||
'index_end' => $viewDatas['index_end'] ?? null,
|
||||
];
|
||||
|
||||
// CodeIgniter의 ResponseTrait의 respond() 메서드를 사용하여 JSON으로 응답
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'message' => "{$this->getTitle()} 목록 API 조회 성공",
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(): string|ResponseInterface
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
|
||||
// API 요청 여부를 확인합니다.
|
||||
// is_api 또는 RequestTemplate 파라미터를 getVar()로 통합하여 확인합니다.
|
||||
$isApiRequest = $this->request->getVar('is_api') === '1' ||
|
||||
$this->request->getVar('RequestTemplate') === 'api';
|
||||
|
||||
try {
|
||||
//초기화
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('uri', $this->request->getUri());
|
||||
|
||||
// Page, Per_page 설정
|
||||
$page = (int) $this->request->getVar('page') ?: 1;
|
||||
$perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10);
|
||||
$this->addViewDatas('page', $page);
|
||||
$this->addViewDatas('perpage', $perpage);
|
||||
|
||||
// index_totalcount를 위한 조건절 처리 (오버라이드 가능)
|
||||
$this->index_condition_process($action);
|
||||
$index_totalcount = $this->service->getTotalCount();
|
||||
$this->addViewDatas('index_totalcount', $index_totalcount);
|
||||
|
||||
// API 요청이 아닌 경우에만 HTML 뷰를 위한 페이지네이션 링크와 옵션을 준비
|
||||
if (!$isApiRequest) {
|
||||
$this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage));
|
||||
$this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage));
|
||||
}
|
||||
|
||||
// 조건절 LIMIT , OFFSET 처리 List용 (기존 로직 유지)
|
||||
$this->index_condition_process($action);
|
||||
$this->service->setLimit($perpage);
|
||||
$this->service->setOffset(($page - 1) * $perpage);
|
||||
|
||||
// Entities 처리 (API든 View든 공통적으로 필요하며, 하위 클래스에서 오버라이드 가능)
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
|
||||
helper(['form']);
|
||||
|
||||
// 인자 없이 getVar() 호출 시, GET, POST, JSON 등 모든 데이터를 가져오며, 없을 경우 빈 배열을 사용합니다.
|
||||
$this->addViewDatas('formDatas', $this->request->getVar() ?? []);
|
||||
} catch (\Exception $e) {
|
||||
// API 요청인 경우 JSON 오류 메시지를 반환
|
||||
if ($isApiRequest) {
|
||||
return $this->failServerError($e->getMessage());
|
||||
}
|
||||
// 일반 요청인 경우 세션 오류 메시지를 설정
|
||||
session()->setFlashdata('message', $e->getMessage());
|
||||
}
|
||||
|
||||
// --- 결과 분기 처리 ---
|
||||
|
||||
if ($isApiRequest) {
|
||||
// API 요청에 대한 JSON 렌더링
|
||||
return $this->index_api_result_process($action);
|
||||
}
|
||||
|
||||
// 현재 URL을 세션에 저장 (일반 View 요청 시에만)
|
||||
$this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : ""));
|
||||
|
||||
// HTML 뷰 렌더링
|
||||
return $this->index_result_process($action);
|
||||
}
|
||||
|
||||
//OUPUT Document 관련
|
||||
protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array
|
||||
{
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download";
|
||||
switch ($document_type) {
|
||||
case 'excel':
|
||||
$file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = IOFactory::createWriter($loaded_data, 'Xlsx');
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
case 'pdf':
|
||||
$file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = new Mpdf($loaded_data);
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
}
|
||||
return array($full_path, $file_name);
|
||||
}
|
||||
|
||||
protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
switch ($output_type) {
|
||||
case 'excel':
|
||||
case 'pdf':
|
||||
helper(['form']);
|
||||
$this->index_condition_process($action);
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
$html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
//data loading
|
||||
$reader = new Html();
|
||||
$loaded_data = $reader->loadFromString($html);
|
||||
list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data);
|
||||
$full_path .= DIRECTORY_SEPARATOR . $file_name;
|
||||
break;
|
||||
default:
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity) {
|
||||
throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다.");
|
||||
}
|
||||
$this->addViewDatas('entity', $entity);
|
||||
list($file_name, $uploaded_filename) = $entity->getDownlaodFile();
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename;
|
||||
break;
|
||||
}
|
||||
return $this->response->download($full_path, null)->setFileName($file_name);
|
||||
}
|
||||
|
||||
// Download
|
||||
final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
try {
|
||||
//초기화
|
||||
$this->action_init_process($action);
|
||||
return $this->download_process($action, $output_type, $uid);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage());
|
||||
}
|
||||
/**
|
||||
* 최종 다운로드 액션입니다.
|
||||
*/
|
||||
final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
try {
|
||||
$this->action_init_process($action);
|
||||
return $this->download_process($action, $output_type, $uid);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
615
app/Controllers/CommonControllerV1.php
Normal file
615
app/Controllers/CommonControllerV1.php
Normal file
@ -0,0 +1,615 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Controllers\BaseController;
|
||||
use App\Libraries\AuthContext;
|
||||
use App\Traits\LogTrait;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use CodeIgniter\HTTP\DownloadResponse;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Html;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
abstract class CommonControllerV1 extends BaseController
|
||||
{
|
||||
use LogTrait;
|
||||
|
||||
private array $_action_paths = [];
|
||||
private array $_viewDatas = [];
|
||||
protected $service = null;
|
||||
private ?string $_title = null;
|
||||
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
parent::initController($request, $response, $logger);
|
||||
}
|
||||
|
||||
final protected function getAuthContext(): AuthContext
|
||||
{
|
||||
return service('myauth')->getAuthContext();
|
||||
}
|
||||
|
||||
final protected function addActionPaths(string $path)
|
||||
{
|
||||
$this->_action_paths[] = $path;
|
||||
}
|
||||
|
||||
final protected function getActionPaths($isArray = true, $delimeter = DIRECTORY_SEPARATOR): array|string
|
||||
{
|
||||
return $isArray ? $this->_action_paths : implode($delimeter, $this->_action_paths);
|
||||
}
|
||||
|
||||
final protected function addViewDatas(string $key, mixed $value)
|
||||
{
|
||||
$this->_viewDatas[$key] = $value;
|
||||
}
|
||||
|
||||
final protected function getViewDatas(?string $key = null): mixed
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->_viewDatas;
|
||||
}
|
||||
return $this->_viewDatas[$key] ?? null;
|
||||
}
|
||||
|
||||
protected function getTitle(): string
|
||||
{
|
||||
if ($this->_title === null) {
|
||||
$this->_title = lang("{$this->service->getClassPaths(false)}.title");
|
||||
}
|
||||
return $this->_title;
|
||||
}
|
||||
|
||||
//공통 필수기능
|
||||
//필수함수
|
||||
//사용자정의 함수
|
||||
protected function action_init_process(string $action): void
|
||||
{
|
||||
$this->addViewDatas('action', $action);
|
||||
$this->addViewDatas('authContext', $this->getAuthContext());
|
||||
$this->addViewDatas('classPath', $this->service->getClassPaths(false));
|
||||
}
|
||||
|
||||
protected function action_modal_process(string $message): string
|
||||
{
|
||||
return "
|
||||
<script>
|
||||
// IMPORTANT: The original alert() should be replaced with a custom modal/toast in production environment.
|
||||
// alert('" . addslashes($message) . "');
|
||||
|
||||
// Bootstrap 5 모달 닫기
|
||||
var modalEl = document.getElementById('modal_action_form');
|
||||
if (modalEl) {
|
||||
var modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (!modal) {
|
||||
modal = new bootstrap.Modal(modalEl);
|
||||
}
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
// 모달 닫힌 후 부모 페이지 새로고침 (애니메이션 고려)
|
||||
setTimeout(function() {
|
||||
if (window.parent) {
|
||||
window.parent.location.reload();
|
||||
} else if (window.opener && !window.opener.closed) {
|
||||
window.opener.location.reload();
|
||||
}
|
||||
}, 500);
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
protected function action_redirect_process(string $type, string $message, ?string $redirect_url = null): RedirectResponse
|
||||
{
|
||||
switch ($type) {
|
||||
case 'warning':
|
||||
case 'error':
|
||||
case 'critical':
|
||||
case 'alert':
|
||||
case 'emergency':
|
||||
log_message($type, $message);
|
||||
$result = $redirect_url ? $result = redirect()->to($redirect_url)->with('message', $message) : redirect()->back()->withInput()->with('message', $message);
|
||||
break;
|
||||
case 'debug':
|
||||
case 'info':
|
||||
case 'notice':
|
||||
default:
|
||||
log_message($type, $message);
|
||||
$redirect_url = $redirect_url ?? $this->getAuthContext()->popPreviousUrl() ?? implode(DIRECTORY_SEPARATOR, $this->getActionPaths());
|
||||
$result = redirect()->to($redirect_url)->with('message', $message);
|
||||
break;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function action_render_process(array $view_paths, string $view_file, array $viewDatas): string
|
||||
{
|
||||
$lastest_path = array_pop($view_paths); //paths는 마지막을 뺀 앞단까지만 남음
|
||||
|
||||
$full_path = implode(DIRECTORY_SEPARATOR, $view_paths);
|
||||
|
||||
// GET/POST/JSON 데이터에서 'ActionTemplate' 파라미터를 가져옵니다.
|
||||
$final_path = $this->request->getVar('ActionTemplate');
|
||||
|
||||
if ($final_path) {
|
||||
$full_path .= DIRECTORY_SEPARATOR . $final_path;
|
||||
}
|
||||
|
||||
$view_datas = [
|
||||
...$viewDatas,
|
||||
'forms' => ['attributes' => ['method' => "post",], 'hiddens' => []],
|
||||
];
|
||||
|
||||
helper(['form', __FUNCTION__]);
|
||||
return view($full_path . DIRECTORY_SEPARATOR . $view_file, ['viewDatas' => $view_datas]);
|
||||
}
|
||||
|
||||
//생성
|
||||
protected function create_form_process(array $formDatas = []): array
|
||||
{
|
||||
//Form Default값 설정
|
||||
return $formDatas;
|
||||
}
|
||||
|
||||
protected function create_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function create_form(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('formDatas', $this->create_form_process());
|
||||
return $this->create_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function create_process(): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환 (getPost()는 POST 요청 본문만 가져옵니다.)
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->create($dto);
|
||||
}
|
||||
|
||||
protected function create_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 생성이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function create(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->create_result_process($this->create_process());
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 검증오류:" . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 생성 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//수정
|
||||
protected function modify_form_process($uid): CommonEntity
|
||||
{
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function modify_form_result_process(string $action): string|RedirectResponse
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function modify_form($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->modify_form_process($uid));
|
||||
return $this->modify_form_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정폼 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function modify_process($uid): CommonEntity
|
||||
{
|
||||
// POST 데이터를 DTO 객체로 변환
|
||||
$dto = $this->service->createDTO($this->request->getPost());
|
||||
return $this->service->modify($uid, $dto);
|
||||
}
|
||||
|
||||
protected function modify_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_modal_process("{$this->getTitle()}에서 {$entity->getTitle()} 수정이 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function modify($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->action_init_process(__FUNCTION__);
|
||||
return $this->modify_result_process($this->modify_process($uid));
|
||||
} catch (ValidationException $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//일괄처리
|
||||
protected function batchjob_pre_process(array $postDatas = []): array
|
||||
{
|
||||
// POST 데이터 전체를 가져옵니다.
|
||||
foreach ($this->request->getPost() as $field => $value) {
|
||||
$postDatas[$field] = $value;
|
||||
}
|
||||
//1. postDatas에서 선택된 uids 정보 추출
|
||||
$uids = [];
|
||||
if (isset($postDatas['batchjob_uids'])) {
|
||||
$uids = $postDatas['batchjob_uids'];
|
||||
unset($postDatas['batchjob_uids']);
|
||||
}
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("적용할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
//2. postDatas에서 필요없는 정보 버림
|
||||
unset($postDatas['batchjob_submit']);
|
||||
//3. 나머지는 $formDatas로 사용할 데이터 추출
|
||||
$formDatas = [];
|
||||
foreach ($postDatas as $field => $value) {
|
||||
if ($value !== "") {
|
||||
$formDatas[$field] = $value;
|
||||
}
|
||||
}
|
||||
if (empty($formDatas)) {
|
||||
throw new \Exception("변경할 조건항목을 선택하셔야합니다.");
|
||||
}
|
||||
//4. 데이터가 있는 필드 추출
|
||||
$selectedFields = array_keys($formDatas);
|
||||
return array($uids, $selectedFields, $formDatas);
|
||||
}
|
||||
|
||||
protected function batchjob_process($uid, array $formDatas): CommonEntity
|
||||
{
|
||||
return $this->service->batchjob($uid, $formDatas);
|
||||
}
|
||||
|
||||
protected function batchjob_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 수정이 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
final public function batchjob(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
//사전작업
|
||||
list($uids, $selectedFields, $formDatas) = $this->batchjob_pre_process();
|
||||
//초기화
|
||||
$this->service->getFormService()->setFormFields($selectedFields);
|
||||
$this->service->getFormService()->setFormRules(__FUNCTION__, $selectedFields);
|
||||
$this->service->getFormService()->setFormFilters($selectedFields);
|
||||
$this->service->getFormService()->setFormOptions($selectedFields);
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$entities[] = $this->batchjob_process($uid, $formDatas);
|
||||
} catch (ValidationException $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 검증오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 수정 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄작업처리 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//삭제
|
||||
protected function delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
protected function delete_result_process(CommonEntity $entity): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', "{$this->getTitle()}에서 {$entity->getTitle()} 삭제가 완료되었습니다.");
|
||||
}
|
||||
|
||||
final public function delete($uid): RedirectResponse
|
||||
{
|
||||
try {
|
||||
return $this->delete_result_process($this->delete_process($uid));
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//일괄삭제
|
||||
protected function batchjob_delete_pre_process(): array
|
||||
{
|
||||
// POST 데이터 전체를 가져옵니다.
|
||||
$postDatas = $this->request->getPost();
|
||||
//1. postDatas에서 선택된 uids 정보 추출
|
||||
$uids = [];
|
||||
if (isset($postDatas['batchjob_uids'])) {
|
||||
$uids = $postDatas['batchjob_uids'];
|
||||
unset($postDatas['batchjob_uids']);
|
||||
}
|
||||
if (empty($uids)) {
|
||||
throw new \Exception("삭제할 리스트을 선택하셔야합니다.");
|
||||
}
|
||||
return array($uids);
|
||||
}
|
||||
|
||||
protected function batchjob_delete_result_process(array $uids, array $entities, array $errors): string|RedirectResponse
|
||||
{
|
||||
return $this->action_redirect_process('info', sprintf(
|
||||
"%s에서 %s개 처리완료, %s개 오류, 총:%s개 일괄삭제가 완료되었습니다.",
|
||||
$this->getTitle(),
|
||||
count($entities),
|
||||
count($errors),
|
||||
count($uids)
|
||||
));
|
||||
}
|
||||
|
||||
protected function batchjob_delete_process($uid): CommonEntity
|
||||
{
|
||||
return $this->service->delete($uid);
|
||||
}
|
||||
|
||||
final public function batchjob_delete(): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$uids = $this->batchjob_delete_pre_process();
|
||||
$entities = [];
|
||||
$errors = [];
|
||||
foreach ($uids as $uid) {
|
||||
try {
|
||||
$entities[] = $this->batchjob_delete_process($uid);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', "{$this->getTitle()}에서 {$uid} 삭제 오류:" . $e->getMessage());
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
return $this->batchjob_delete_result_process($uids, $entities, $errors);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 일괄삭제 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//상세보기
|
||||
protected function view_process($uid): CommonEntity
|
||||
{
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$this->getTitle()}에 번호가 정의 되지 않았습니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity instanceof CommonEntity) {
|
||||
throw new \Exception("{$uid}에 해당하는 {$this->getTitle()}을 찾을수 없습니다.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function view_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
|
||||
final public function view($uid): string|RedirectResponse
|
||||
{
|
||||
try {
|
||||
$action = __FUNCTION__;
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('entity', $this->view_process($uid));
|
||||
return $this->view_result_process($action);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 {$uid} 상세보기 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//리스트관련
|
||||
//조건절 처리
|
||||
protected function index_condition_process(string $action): void
|
||||
{
|
||||
//Filter조건절 처리
|
||||
$index_filters = [];
|
||||
foreach ($this->service->getFormService()->getFormFilters($action) as $field) {
|
||||
// getVar()를 사용하여 GET, POST, JSON 입력을 통합하여 가져옵니다.
|
||||
$value = $this->request->getVar($field) ?? null;
|
||||
if ($value) {
|
||||
$this->service->setFilter($field, $value);
|
||||
$index_filters[$field] = $value;
|
||||
}
|
||||
}
|
||||
$this->addViewDatas('index_filters', $index_filters);
|
||||
|
||||
//검색어조건절 처리
|
||||
$index_word = $this->request->getVar('index_word');
|
||||
if ($index_word !== null && $index_word !== '') {
|
||||
$this->service->setSearchWord($index_word);
|
||||
}
|
||||
$this->addViewDatas('index_word', $index_word);
|
||||
|
||||
//날자검색
|
||||
$index_start = $this->request->getVar('index_start');
|
||||
$index_end = $this->request->getVar('index_end');
|
||||
if ($index_start !== null && $index_start !== '' && $index_end !== null && $index_end !== '') {
|
||||
$this->service->setDateFilter($index_start, $index_end);
|
||||
}
|
||||
$this->addViewDatas('index_start', $index_start);
|
||||
$this->addViewDatas('index_end', $index_end);
|
||||
|
||||
//OrcerBy처리
|
||||
$order_field = $this->request->getVar('order_field');
|
||||
$order_value = $this->request->getVar('order_value');
|
||||
$this->service->setOrderBy($order_field, $order_value);
|
||||
$this->addViewDatas('order_field', $order_field);
|
||||
$this->addViewDatas('order_value', $order_value);
|
||||
}
|
||||
|
||||
//Index Option출력용
|
||||
protected function pagenation_options_process(int $index_totalcount, int $perpage): array
|
||||
{
|
||||
$page_options = ["" => "줄수선택"];
|
||||
for ($i = $perpage; $i <= $index_totalcount; $i += $perpage) {
|
||||
$page_options[$i] = $i;
|
||||
}
|
||||
$page_options[$index_totalcount] = $index_totalcount;
|
||||
return $page_options;
|
||||
}
|
||||
|
||||
//PageNation 처리
|
||||
protected function pagenation_process(int $index_totalcount, int $page, int $perpage, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): mixed
|
||||
{
|
||||
// 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성
|
||||
// 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full',
|
||||
// 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가
|
||||
$pager = service("pager");
|
||||
$pager->makeLinks($page, $perpage, $index_totalcount, $template, $segment, $pager_group);
|
||||
// $page = $pager->getCurrentPage($pager_group);
|
||||
$this->addViewDatas('index_totalpage', $pager->getPageCount($pager_group));
|
||||
return $pager->links($pager_group, $template);
|
||||
}
|
||||
|
||||
//Entities처리 (하위 클래스에서 오버라이드 가능)
|
||||
protected function index_process(array $entities = []): array
|
||||
{
|
||||
foreach ($this->service->getEntities() as $entity) {
|
||||
$entities[] = $entity;
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
// HTML View 출력 처리 (기존 로직)
|
||||
protected function index_result_process(string $action): string
|
||||
{
|
||||
return $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
}
|
||||
public function index(): string|ResponseInterface
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
try {
|
||||
//초기화
|
||||
$this->action_init_process($action);
|
||||
$this->addViewDatas('uri', $this->request->getUri());
|
||||
// Page, Per_page 설정
|
||||
$page = (int) $this->request->getVar('page') ?: 1;
|
||||
$perpage = (int) $this->request->getVar('perpage') ?: intval(DEFAULTS['INDEX_PERPAGE'] ?? 10);
|
||||
$this->addViewDatas('page', $page);
|
||||
$this->addViewDatas('perpage', $perpage);
|
||||
|
||||
// index_totalcount를 위한 조건절 처리 (오버라이드 가능)
|
||||
$this->index_condition_process($action);
|
||||
$index_totalcount = $this->service->getTotalCount();
|
||||
$this->addViewDatas('index_totalcount', $index_totalcount);
|
||||
$this->addViewDatas('index_pagination', $this->pagenation_process($index_totalcount, $page, $perpage));
|
||||
$this->addViewDatas('index_pagination_options', $this->pagenation_options_process($index_totalcount, $perpage));
|
||||
|
||||
// 조건절 LIMIT , OFFSET 처리 List용 (기존 로직 유지)
|
||||
$this->index_condition_process($action);
|
||||
$this->service->setLimit($perpage);
|
||||
$this->service->setOffset(($page - 1) * $perpage);
|
||||
|
||||
// Entities 처리 (API든 View든 공통적으로 필요하며, 하위 클래스에서 오버라이드 가능)
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
helper(['form']);
|
||||
// 인자 없이 getVar() 호출 시, GET, POST, JSON 등 모든 데이터를 가져오며, 없을 경우 빈 배열을 사용합니다.
|
||||
$this->addViewDatas('formDatas', $this->request->getVar() ?? []);
|
||||
} catch (\Exception $e) {
|
||||
// 일반 요청인 경우 세션 오류 메시지를 설정
|
||||
session()->setFlashdata('message', $e->getMessage());
|
||||
}
|
||||
$this->getAuthContext()->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : ""));
|
||||
// HTML 뷰 렌더링
|
||||
return $this->index_result_process($action);
|
||||
}
|
||||
|
||||
//OUPUT Document 관련
|
||||
protected function downloadByDocumentType(string $document_type, mixed $loaded_data): array
|
||||
{
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "download";
|
||||
switch ($document_type) {
|
||||
case 'excel':
|
||||
$file_name = sprintf("%s_%s.xlsx", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = IOFactory::createWriter($loaded_data, 'Xlsx');
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
case 'pdf':
|
||||
$file_name = sprintf("%s_%s.pdf", $this->service->getClassPaths(false, "_"), date('Y-m-d_Hm'));
|
||||
$writer = new Mpdf($loaded_data);
|
||||
$writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
|
||||
break;
|
||||
}
|
||||
return array($full_path, $file_name);
|
||||
}
|
||||
|
||||
protected function download_process(string $action, string $output_type, mixed $uid = null): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
switch ($output_type) {
|
||||
case 'excel':
|
||||
case 'pdf':
|
||||
helper(['form']);
|
||||
$this->index_condition_process($action);
|
||||
$this->addViewDatas('entities', $this->index_process());
|
||||
$html = $this->action_render_process($this->getActionPaths(), $action, $this->getViewDatas());
|
||||
//data loading
|
||||
$reader = new Html();
|
||||
$loaded_data = $reader->loadFromString($html);
|
||||
list($full_path, $file_name) = $this->downloadByDocumentType($output_type, $loaded_data);
|
||||
$full_path .= DIRECTORY_SEPARATOR . $file_name;
|
||||
break;
|
||||
default:
|
||||
if (!$uid) {
|
||||
throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다.");
|
||||
}
|
||||
$entity = $this->service->getEntity($uid);
|
||||
if (!$entity) {
|
||||
throw new \Exception("{$uid}에 대한 정보를 찾을수 없습니다.");
|
||||
}
|
||||
$this->addViewDatas('entity', $entity);
|
||||
list($file_name, $uploaded_filename) = $entity->getDownlaodFile();
|
||||
$full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename;
|
||||
break;
|
||||
}
|
||||
return $this->response->download($full_path, null)->setFileName($file_name);
|
||||
}
|
||||
|
||||
// Download
|
||||
final public function download(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse|string
|
||||
{
|
||||
$action = __FUNCTION__;
|
||||
try {
|
||||
//초기화
|
||||
$this->action_init_process($action);
|
||||
return $this->download_process($action, $output_type, $uid);
|
||||
} catch (\Exception $e) {
|
||||
return $this->action_redirect_process('error', "{$this->getTitle()}에서 오류:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ namespace App\Controllers;
|
||||
|
||||
class Home extends BaseController
|
||||
{
|
||||
public function index(): string
|
||||
public function welcome(): string
|
||||
{
|
||||
return view('welcome_message');
|
||||
}
|
||||
|
||||
148
app/Views/admin/api/index.php
Normal file
148
app/Views/admin/api/index.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
|
||||
<?php
|
||||
// 레이아웃 및 템플릿 경로 설정
|
||||
$layouts = LAYOUTS[$viewDatas['layout']]['path'];
|
||||
$template = "templates" . DIRECTORY_SEPARATOR . "{$viewDatas['layout']}";
|
||||
|
||||
// 데이터 API 엔드포인트 설정 (웹 뷰가 데이터를 요청할 주소)
|
||||
// RequestTemplate=api 파라미터를 사용하여 해당 컨트롤러가 JSON을 반환하도록 유도합니다.
|
||||
$dataApiEndpoint = current_url() . '?RequestTemplate=api';
|
||||
$columnCount = count($viewDatas['formFields']) + 2; // 번호와 작업 컬럼 포함
|
||||
?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
|
||||
<div class="layout_top"><?= $this->include("{$layouts}/top"); ?></div>
|
||||
<table class="layout_middle">
|
||||
<tr>
|
||||
<td class="layout_left"><?= $this->include("{$layouts}/left_menu"); ?></td>
|
||||
<td class="layout_right">
|
||||
<div class="layout_header"><?= $this->include("{$template}/index_header"); ?></div>
|
||||
<div id="container" class="layout_content">
|
||||
|
||||
<link href="/css/<?= $viewDatas['layout'] ?>/index.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<div class="index_body">
|
||||
<!-- 필터 영역은 유지 -->
|
||||
<?= $this->include("{$template}/index_content_filter"); ?>
|
||||
<?= form_open(current_url(), ['id' => 'batchjob_form', 'method' => "post"]) ?>
|
||||
<table class="index_table data table table-bordered table-hover table-striped" data-rtc-resizable-table="reisze_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center bg-light">번호</th>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<th class="text-center bg-light"><?= $viewDatas['helper']->getListLabel($field, $label, $viewDatas) ?></th>
|
||||
<?php endforeach ?>
|
||||
<th class="text-center bg-light">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- API 호출로 데이터를 채울 영역 -->
|
||||
<tbody id="data_list_tbody">
|
||||
<tr>
|
||||
<td colspan="<?= $columnCount ?>" class="text-center text-secondary p-5">목록 데이터를 로드 중입니다...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 페이지네이션 영역 (데이터 로드 후 JS로 업데이트) -->
|
||||
<div id="pagination_area">
|
||||
<?= $this->include("{$template}/index_content_bottom"); ?>
|
||||
</div>
|
||||
|
||||
<?= form_close() ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="layout_footer"><?= $this->include("{$template}/index_footer"); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="layout_bottom"><?= $this->include("{$layouts}/bottom"); ?></div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tbody = document.getElementById('data_list_tbody');
|
||||
const apiEndpoint = '<?= $dataApiEndpoint ?>';
|
||||
const columnCount = <?= $columnCount ?>;
|
||||
const formFields = <?= json_encode(array_keys($viewDatas['formFields'])) ?>;
|
||||
const actionButtons = <?= json_encode($viewDatas['index_actionButtons']) ?>;
|
||||
|
||||
// 페이지 로드 시 또는 필터링/페이지네이션 시 호출될 메인 함수
|
||||
window.fetchData = async function(page = 1, filters = {}) {
|
||||
// 로딩 상태 표시
|
||||
tbody.innerHTML = `<tr><td colspan="${columnCount}" class="text-center text-info p-5">데이터 로드 중...</td></tr>`;
|
||||
|
||||
// 쿼리 파라미터 구성 (페이지, 필터 등을 포함)
|
||||
const url = new URL(apiEndpoint);
|
||||
url.searchParams.set('page', page);
|
||||
Object.keys(filters).forEach(key => url.searchParams.set(key, filters[key]));
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API 호출 실패: ${response.statusText}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.status === 'success' && Array.isArray(result.data)) {
|
||||
renderTable(result.data, result.pagination);
|
||||
} else {
|
||||
throw new Error(result.message || '데이터 구조 오류');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("데이터 로드 오류:", error);
|
||||
tbody.innerHTML = `<tr><td colspan="${columnCount}" class="text-center text-danger p-5">목록을 불러오는 데 실패했습니다. (${error.message})</td></tr>`;
|
||||
}
|
||||
}
|
||||
// 테이블 렌더링 함수
|
||||
function renderTable(entities, pagination) {
|
||||
if (entities.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="${columnCount}" class="text-center text-muted p-5">조회된 데이터가 없습니다.</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
// 총 건수와 현재 페이지 정보로 시작 번호를 계산해야 하지만, 여기서는 단순화합니다.
|
||||
let totalCount = pagination ? parseInt(pagination.totalCount) : entities.length;
|
||||
let perPage = pagination ? parseInt(pagination.perPage) : 10;
|
||||
let page = pagination ? parseInt(pagination.page) : 1;
|
||||
|
||||
let startNum = totalCount - ((page - 1) * perPage);
|
||||
|
||||
entities.forEach((entity, index) => {
|
||||
html += '<tr>';
|
||||
|
||||
// 1. 번호
|
||||
let currentNum = startNum - index;
|
||||
// $viewDatas['helper']->getListButton('modify', $num, $viewDatas) 역할을 하는 단순 링크
|
||||
const modifyLink = `/modify/${entity.id || ''}`;
|
||||
html += `<td nowrap><a href="${modifyLink}">${currentNum}</a></td>`;
|
||||
|
||||
// 2. 데이터 필드
|
||||
formFields.forEach(field => {
|
||||
const fieldValue = entity[field] !== undefined ? entity[field] : '';
|
||||
// 이 부분에서 기존 PHP 헬퍼($viewDatas['helper']->getFieldView)의 복잡한 로직을 JS로 재구현해야 함
|
||||
html += `<td>${fieldValue}</td>`;
|
||||
});
|
||||
|
||||
// 3. 작업 버튼
|
||||
html += '<td nowrap>';
|
||||
Object.keys(actionButtons).forEach(action => {
|
||||
const label = actionButtons[action];
|
||||
// 작업 버튼 링크 생성
|
||||
const actionLink = `/${action}/${entity.id || ''}`;
|
||||
html += `<a href="${actionLink}" class="btn btn-sm btn-outline-secondary me-1">${label}</a>`;
|
||||
});
|
||||
html += '</td>';
|
||||
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
// pagination 영역 업데이트 로직 추가 필요
|
||||
// document.getElementById('pagination_area').innerHTML = generatePaginationHtml(pagination);
|
||||
}
|
||||
|
||||
// 초기 데이터 로드
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
@ -1,148 +1,58 @@
|
||||
<?= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
|
||||
<?php
|
||||
// PHP 변수들은 그대로 사용하지만, 데이터(entities)는 비어있는 상태로 전달됩니다.
|
||||
$layouts = LAYOUTS[$viewDatas['layout']]['path'];
|
||||
$layouts = LAYOUTS[$viewDatas['layout']];
|
||||
$template = "templates" . DIRECTORY_SEPARATOR . "{$viewDatas['layout']}";
|
||||
|
||||
// API 엔드포인트를 JS에서 사용하기 위해 PHP에서 설정합니다.
|
||||
// 예: '/api/items' 또는 $viewDatas['apiEndpoint'] 변수를 사용
|
||||
$apiEndpoint = '/api/example/listitems';
|
||||
?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= session('message') ? $viewDatas['helper']->alertTrait(session('message')) : ""; ?>
|
||||
|
||||
<div class="layout_top"><?= $this->include("{$layouts}/top"); ?></div>
|
||||
<div class="layout_top"><?= $this->include("{$layouts['path']}/top"); ?></div>
|
||||
<table class="layout_middle">
|
||||
<tr>
|
||||
<td class="layout_left"><?= $this->include("{$layouts}/left_menu"); ?></td>
|
||||
<td class="layout_right">
|
||||
<div class="layout_header"><?= $this->include("{$template}/index_header"); ?></div>
|
||||
<div id="container" class="layout_content">
|
||||
|
||||
<tr>
|
||||
<td class="layout_left"><?= $this->include("{$layouts['path']}/left_menu"); ?></td>
|
||||
<td class="layout_right">
|
||||
<div class="layout_header"><?= $this->include("{$template}/index_header"); ?></div>
|
||||
<div id="container" class="layout_content">
|
||||
<link href="/css/<?= $viewDatas['layout'] ?>/index.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<div class="index_body">
|
||||
<?= $this->include("{$template}/index_content_filter"); ?>
|
||||
<?= form_open(current_url(), ['id' => 'batchjob_form', 'method' => "post"]) ?>
|
||||
<table class="index_table data table table-bordered table-hover table-striped" data-rtc-resizable-table="reisze_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center bg-light">번호</th>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<th class="text-center bg-light"><?= $viewDatas['helper']->getListLabel($field, $label, $viewDatas) ?></th>
|
||||
<?php endforeach ?>
|
||||
<th class="text-center bg-light">작업</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- 데이터가 동적으로 채워질 영역: 기존 PHP 루프 대신 ID 부여 -->
|
||||
<tbody id="data_list_tbody">
|
||||
<tr>
|
||||
<td colspan="<?= count($viewDatas['formFields']) + 2 ?>" class="text-center text-secondary p-5">데이터를 불러오는 중입니다...</td>
|
||||
<div class="index_body">
|
||||
<?= $this->include("{$template}/index_content_filter"); ?>
|
||||
<?= form_open(current_url(), ['id' => 'batchjob_form', 'method' => "post"]) ?>
|
||||
<table class="index_table data table table-bordered table-hover table-striped" data-rtc-resizable-table="reisze_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center bg-light">번호</th>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?>
|
||||
<th class="text-center bg-light"><?= $viewDatas['helper']->getListLabel($field, $label, $viewDatas) ?></th>
|
||||
<?php endforeach ?>
|
||||
<th class="text-center bg-light">작업</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?= $this->include("{$template}/index_content_bottom"); ?>
|
||||
<?= form_close() ?>
|
||||
</div>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $cnt = 0 ?>
|
||||
<?php foreach ($viewDatas['entities'] as $entity): ?>
|
||||
<?php
|
||||
$viewDatas['entity'] = $entity;
|
||||
$num = $viewDatas['index_totalcount'] - (($viewDatas['page'] - 1) * $viewDatas['perpage'] + $cnt);
|
||||
?>
|
||||
<tr>
|
||||
<td nowrap><?= $viewDatas['helper']->getListButton('modify', $num, $viewDatas) ?></td>
|
||||
<?php foreach ($viewDatas['formFields'] as $field => $label): ?><td><?= $viewDatas['helper']->getFieldView($field, $entity->$field, $viewDatas) ?></td><?php endforeach ?>
|
||||
<td nowrap>
|
||||
<?php foreach ($viewDatas['index_actionButtons'] as $action => $label): ?>
|
||||
<?= $viewDatas['helper']->getListButton($action, $label, $viewDatas) ?>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php $cnt++ ?>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?= $this->include("{$template}/index_content_bottom"); ?>
|
||||
<?= form_close() ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout_footer"><?= $this->include("{$template}/index_footer"); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="layout_footer"><?= $this->include("{$template}/index_footer"); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="layout_bottom"><?= $this->include("{$layouts}/bottom"); ?></div>
|
||||
|
||||
<!-- 2. API Call JavaScript 추가 -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tbody = document.getElementById('data_list_tbody');
|
||||
const apiEndpoint = '<?= $apiEndpoint ?>'; // PHP에서 설정한 API 경로
|
||||
|
||||
async function fetchData() {
|
||||
// 로딩 상태 표시
|
||||
tbody.innerHTML = `<tr><td colspan="<?= count($viewDatas['formFields']) + 2 ?>" class="text-center text-info p-5">데이터 로드 중...</td></tr>`;
|
||||
|
||||
try {
|
||||
const response = await fetch(apiEndpoint);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('API 호출 실패: ' + response.statusText);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// API 응답 구조를 확인하여 데이터 배열을 추출 (여기서는 result.data라고 가정)
|
||||
if (result.status === 'success' && Array.isArray(result.data)) {
|
||||
renderTable(result.data);
|
||||
} else {
|
||||
throw new Error(result.message || '데이터 구조 오류');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("데이터 로드 오류:", error);
|
||||
tbody.innerHTML = `<tr><td colspan="<?= count($viewDatas['formFields']) + 2 ?>" class="text-center text-danger p-5">목록을 불러오는 데 실패했습니다. (${error.message})</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API에서 받은 데이터 배열을 기반으로 테이블 본문을 렌더링합니다.
|
||||
* * 주의: 이 부분은 클라이언트(JS)에서 HTML을 생성하므로,
|
||||
* 기존 PHP Helper($viewDatas['helper'])를 사용할 수 없어 순수 HTML 문자열을 구성해야 합니다.
|
||||
* 동적 필드 렌더링($viewDatas['helper']->getFieldView) 로직은 JS로 옮겨야 합니다.
|
||||
* 아래는 **가장 단순화된 예시**입니다.
|
||||
*/
|
||||
function renderTable(entities) {
|
||||
if (entities.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="<?= count($viewDatas['formFields']) + 2 ?>" class="text-center text-muted p-5">조회된 데이터가 없습니다.</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
// 실제로는 전체 건수와 페이지당 개수를 알아야 정확한 번호를 계산할 수 있지만,
|
||||
// 여기서는 예시로 인덱스 기반 번호를 사용합니다.
|
||||
let num = entities.length;
|
||||
|
||||
const formFields = <?= json_encode(array_keys($viewDatas['formFields'])) ?>;
|
||||
|
||||
entities.forEach((entity, index) => {
|
||||
html += '<tr>';
|
||||
|
||||
// 1. 번호 (getListButton('modify', ...)) - 단순화
|
||||
// 원본 코드를 완벽히 재현하려면 modify 버튼 로직을 JS로 구현해야 합니다.
|
||||
html += `<td nowrap>${num - index}</td>`;
|
||||
|
||||
// 2. 데이터 필드
|
||||
formFields.forEach(field => {
|
||||
// $entity->$field 대신 entity[field] 사용.
|
||||
// $viewDatas['helper']->getFieldView 로직을 단순하게 출력으로 대체.
|
||||
// 실제 프로젝트에서는 이 부분을 복잡한 UI 컴포넌트(모달, 링크 등)로 변환하는 JS 로직이 필요합니다.
|
||||
const fieldValue = entity[field] !== undefined ? entity[field] : '';
|
||||
html += `<td>${fieldValue}</td>`;
|
||||
});
|
||||
|
||||
// 3. 작업 버튼 (index_actionButtons)
|
||||
// 원본 코드를 완벽히 재현하려면 getListButton 로직을 JS로 구현해야 합니다.
|
||||
html += '<td nowrap>';
|
||||
<?php foreach ($viewDatas['index_actionButtons'] as $action => $label): ?>
|
||||
// 버튼 HTML을 직접 삽입 (예: 상세 보기 버튼)
|
||||
html += `<a href="/${apiEndpoint.split('/')[1]}/${entity.id || ''}/${action}" class="btn btn-sm btn-outline-secondary me-1"><?= $label ?></a>`;
|
||||
<?php endforeach ?>
|
||||
html += '</td>';
|
||||
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
// 테이블 리사이즈 플러그인 재실행 (필요하다면)
|
||||
// if (typeof $.fn.resizableTable === 'function') {
|
||||
// $('.index_table').resizableTable();
|
||||
// }
|
||||
}
|
||||
|
||||
// 페이지 로드 시 데이터 호출 시작
|
||||
fetchData();
|
||||
|
||||
// 필터링이나 페이지네이션 버튼 클릭 시 fetchData를 재호출하도록 연결해야 합니다.
|
||||
// 예: document.getElementById('filter_search_button').addEventListener('click', fetchData);
|
||||
});
|
||||
</script>
|
||||
<div class="layout_bottom"><?= $this->include("{$layouts['path']}/bottom"); ?></div>
|
||||
<?= $this->endSection() ?>
|
||||
Loading…
Reference in New Issue
Block a user