commit
6e6ed71659
84 changed files with 46719 additions and 0 deletions
-
2.gitattributes
-
33.gitignore
-
3.mvn/wrapper/maven-wrapper.properties
-
295mvnw
-
189mvnw.cmd
-
97pom.xml
-
13src/main/java/com/gcc_manpower/GccManpowerApplication.java
-
30src/main/java/com/gcc_manpower/config/DataSourceConfig.java
-
559src/main/java/com/gcc_manpower/controller/HomeController.java
-
836src/main/java/com/gcc_manpower/service/AppUserService.java
-
37src/main/resources/application-local.properties
-
44src/main/resources/application-prod.properties
-
5src/main/resources/application.properties
-
11794src/main/resources/static/assets/bootstrap-5.3.2/css/bootstrap.min.css
-
2212src/main/resources/static/assets/bootstrap-5.3.2/js/bootstrap.min.js
-
193src/main/resources/static/assets/css/body-form-styles.css
-
114src/main/resources/static/assets/css/datatable-styles.css
-
77src/main/resources/static/assets/css/header-styles.css
-
826src/main/resources/static/assets/css/home-styles.css
-
272src/main/resources/static/assets/css/login-form-styles.css
-
532src/main/resources/static/assets/datatables-1.13.6/css/dataTables.bootstrap5.min.css
-
80src/main/resources/static/assets/datatables-1.13.6/js/dataTables.bootstrap5.min.js
-
2990src/main/resources/static/assets/datatables-1.13.6/js/jquery.dataTables.min.js
-
248src/main/resources/static/assets/datatables-buttons-2.4.1/css/buttons.bootstrap5.min.css
-
70src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.bootstrap5.min.js
-
538src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js
-
79src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.print.min.js
-
764src/main/resources/static/assets/datatables-buttons-2.4.1/js/dataTables.buttons.min.js
-
6995src/main/resources/static/assets/font-awesome-6.5.1/css/all.min.css
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-brands-400.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-regular-400.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-solid-900.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.1/webfonts/fa-v4compatibility.woff2
-
7023src/main/resources/static/assets/font-awesome-6.5.2/css/all.min.css
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-brands-400.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-regular-400.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-solid-900.woff2
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.ttf
-
BINsrc/main/resources/static/assets/font-awesome-6.5.2/webfonts/fa-v4compatibility.woff2
-
BINsrc/main/resources/static/assets/header-images/gcc1.png
-
3244src/main/resources/static/assets/jquery-3.6.4/js/jquery-3.6.4.min.js
-
93src/main/resources/static/assets/js/add-url-data-script.js
-
23src/main/resources/static/assets/js/fetch-csrf-script.js
-
3src/main/resources/static/assets/js/go-back-script.js
-
426src/main/resources/static/assets/js/home-script.js
-
28src/main/resources/static/assets/js/login-script.js
-
136src/main/resources/static/assets/js/update-url-data-script.js
-
56src/main/resources/static/assets/js/view-login-details-script.js
-
1123src/main/resources/static/assets/popper-2.11.8/js/popper.min.js
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-100.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-200.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-300.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-500.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-600.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/devanagari/poppins-v23-devanagari-regular.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-100.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-200.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-300.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-500.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-600.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-700.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-regular.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-100.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-200.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-300.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-500.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-600.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-700.woff2
-
BINsrc/main/resources/static/assets/poppins-23.0/fonts/latin/poppins-v23-latin-regular.woff2
-
21src/main/resources/templates/fragments/footer.html
-
22src/main/resources/templates/fragments/header.html
-
79src/main/resources/templates/fragments/navbar.html
-
3456src/main/resources/templates/home/home.html
-
88src/main/resources/templates/home/login.html
-
941src/main/resources/templates/home/view-login-details.html
-
30src/main/resources/templates/modals/login-modal.html
@ -0,0 +1,2 @@ |
|||||
|
/mvnw text eol=lf |
||||
|
*.cmd text eol=crlf |
||||
@ -0,0 +1,33 @@ |
|||||
|
HELP.md |
||||
|
target/ |
||||
|
.mvn/wrapper/maven-wrapper.jar |
||||
|
!**/src/main/**/target/ |
||||
|
!**/src/test/**/target/ |
||||
|
|
||||
|
### STS ### |
||||
|
.apt_generated |
||||
|
.classpath |
||||
|
.factorypath |
||||
|
.project |
||||
|
.settings |
||||
|
.springBeans |
||||
|
.sts4-cache |
||||
|
|
||||
|
### IntelliJ IDEA ### |
||||
|
.idea |
||||
|
*.iws |
||||
|
*.iml |
||||
|
*.ipr |
||||
|
|
||||
|
### NetBeans ### |
||||
|
/nbproject/private/ |
||||
|
/nbbuild/ |
||||
|
/dist/ |
||||
|
/nbdist/ |
||||
|
/.nb-gradle/ |
||||
|
build/ |
||||
|
!**/src/main/**/build/ |
||||
|
!**/src/test/**/build/ |
||||
|
|
||||
|
### VS Code ### |
||||
|
.vscode/ |
||||
@ -0,0 +1,3 @@ |
|||||
|
wrapperVersion=3.3.4 |
||||
|
distributionType=only-script |
||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip |
||||
@ -0,0 +1,295 @@ |
|||||
|
#!/bin/sh |
||||
|
# ---------------------------------------------------------------------------- |
||||
|
# Licensed to the Apache Software Foundation (ASF) under one |
||||
|
# or more contributor license agreements. See the NOTICE file |
||||
|
# distributed with this work for additional information |
||||
|
# regarding copyright ownership. The ASF licenses this file |
||||
|
# to you under the Apache License, Version 2.0 (the |
||||
|
# "License"); you may not use this file except in compliance |
||||
|
# with the License. You may obtain a copy of the License at |
||||
|
# |
||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
# |
||||
|
# Unless required by applicable law or agreed to in writing, |
||||
|
# software distributed under the License is distributed on an |
||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
|
# KIND, either express or implied. See the License for the |
||||
|
# specific language governing permissions and limitations |
||||
|
# under the License. |
||||
|
# ---------------------------------------------------------------------------- |
||||
|
|
||||
|
# ---------------------------------------------------------------------------- |
||||
|
# Apache Maven Wrapper startup batch script, version 3.3.4 |
||||
|
# |
||||
|
# Optional ENV vars |
||||
|
# ----------------- |
||||
|
# JAVA_HOME - location of a JDK home dir, required when download maven via java source |
||||
|
# MVNW_REPOURL - repo url base for downloading maven distribution |
||||
|
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven |
||||
|
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output |
||||
|
# ---------------------------------------------------------------------------- |
||||
|
|
||||
|
set -euf |
||||
|
[ "${MVNW_VERBOSE-}" != debug ] || set -x |
||||
|
|
||||
|
# OS specific support. |
||||
|
native_path() { printf %s\\n "$1"; } |
||||
|
case "$(uname)" in |
||||
|
CYGWIN* | MINGW*) |
||||
|
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" |
||||
|
native_path() { cygpath --path --windows "$1"; } |
||||
|
;; |
||||
|
esac |
||||
|
|
||||
|
# set JAVACMD and JAVACCMD |
||||
|
set_java_home() { |
||||
|
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched |
||||
|
if [ -n "${JAVA_HOME-}" ]; then |
||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ]; then |
||||
|
# IBM's JDK on AIX uses strange locations for the executables |
||||
|
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
|
JAVACCMD="$JAVA_HOME/jre/sh/javac" |
||||
|
else |
||||
|
JAVACMD="$JAVA_HOME/bin/java" |
||||
|
JAVACCMD="$JAVA_HOME/bin/javac" |
||||
|
|
||||
|
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then |
||||
|
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 |
||||
|
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 |
||||
|
return 1 |
||||
|
fi |
||||
|
fi |
||||
|
else |
||||
|
JAVACMD="$( |
||||
|
'set' +e |
||||
|
'unset' -f command 2>/dev/null |
||||
|
'command' -v java |
||||
|
)" || : |
||||
|
JAVACCMD="$( |
||||
|
'set' +e |
||||
|
'unset' -f command 2>/dev/null |
||||
|
'command' -v javac |
||||
|
)" || : |
||||
|
|
||||
|
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then |
||||
|
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 |
||||
|
return 1 |
||||
|
fi |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# hash string like Java String::hashCode |
||||
|
hash_string() { |
||||
|
str="${1:-}" h=0 |
||||
|
while [ -n "$str" ]; do |
||||
|
char="${str%"${str#?}"}" |
||||
|
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) |
||||
|
str="${str#?}" |
||||
|
done |
||||
|
printf %x\\n $h |
||||
|
} |
||||
|
|
||||
|
verbose() { :; } |
||||
|
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } |
||||
|
|
||||
|
die() { |
||||
|
printf %s\\n "$1" >&2 |
||||
|
exit 1 |
||||
|
} |
||||
|
|
||||
|
trim() { |
||||
|
# MWRAPPER-139: |
||||
|
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. |
||||
|
# Needed for removing poorly interpreted newline sequences when running in more |
||||
|
# exotic environments such as mingw bash on Windows. |
||||
|
printf "%s" "${1}" | tr -d '[:space:]' |
||||
|
} |
||||
|
|
||||
|
scriptDir="$(dirname "$0")" |
||||
|
scriptName="$(basename "$0")" |
||||
|
|
||||
|
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties |
||||
|
while IFS="=" read -r key value; do |
||||
|
case "${key-}" in |
||||
|
distributionUrl) distributionUrl=$(trim "${value-}") ;; |
||||
|
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; |
||||
|
esac |
||||
|
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" |
||||
|
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" |
||||
|
|
||||
|
case "${distributionUrl##*/}" in |
||||
|
maven-mvnd-*bin.*) |
||||
|
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ |
||||
|
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in |
||||
|
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; |
||||
|
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;; |
||||
|
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;; |
||||
|
:Linux*x86_64*) distributionPlatform=linux-amd64 ;; |
||||
|
*) |
||||
|
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 |
||||
|
distributionPlatform=linux-amd64 |
||||
|
;; |
||||
|
esac |
||||
|
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" |
||||
|
;; |
||||
|
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; |
||||
|
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; |
||||
|
esac |
||||
|
|
||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME |
||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> |
||||
|
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" |
||||
|
distributionUrlName="${distributionUrl##*/}" |
||||
|
distributionUrlNameMain="${distributionUrlName%.*}" |
||||
|
distributionUrlNameMain="${distributionUrlNameMain%-bin}" |
||||
|
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" |
||||
|
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" |
||||
|
|
||||
|
exec_maven() { |
||||
|
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : |
||||
|
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" |
||||
|
} |
||||
|
|
||||
|
if [ -d "$MAVEN_HOME" ]; then |
||||
|
verbose "found existing MAVEN_HOME at $MAVEN_HOME" |
||||
|
exec_maven "$@" |
||||
|
fi |
||||
|
|
||||
|
case "${distributionUrl-}" in |
||||
|
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; |
||||
|
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; |
||||
|
esac |
||||
|
|
||||
|
# prepare tmp dir |
||||
|
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then |
||||
|
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } |
||||
|
trap clean HUP INT TERM EXIT |
||||
|
else |
||||
|
die "cannot create temp dir" |
||||
|
fi |
||||
|
|
||||
|
mkdir -p -- "${MAVEN_HOME%/*}" |
||||
|
|
||||
|
# Download and Install Apache Maven |
||||
|
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." |
||||
|
verbose "Downloading from: $distributionUrl" |
||||
|
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" |
||||
|
|
||||
|
# select .zip or .tar.gz |
||||
|
if ! command -v unzip >/dev/null; then |
||||
|
distributionUrl="${distributionUrl%.zip}.tar.gz" |
||||
|
distributionUrlName="${distributionUrl##*/}" |
||||
|
fi |
||||
|
|
||||
|
# verbose opt |
||||
|
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' |
||||
|
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v |
||||
|
|
||||
|
# normalize http auth |
||||
|
case "${MVNW_PASSWORD:+has-password}" in |
||||
|
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; |
||||
|
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; |
||||
|
esac |
||||
|
|
||||
|
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then |
||||
|
verbose "Found wget ... using wget" |
||||
|
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" |
||||
|
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then |
||||
|
verbose "Found curl ... using curl" |
||||
|
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" |
||||
|
elif set_java_home; then |
||||
|
verbose "Falling back to use Java to download" |
||||
|
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" |
||||
|
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" |
||||
|
cat >"$javaSource" <<-END |
||||
|
public class Downloader extends java.net.Authenticator |
||||
|
{ |
||||
|
protected java.net.PasswordAuthentication getPasswordAuthentication() |
||||
|
{ |
||||
|
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); |
||||
|
} |
||||
|
public static void main( String[] args ) throws Exception |
||||
|
{ |
||||
|
setDefault( new Downloader() ); |
||||
|
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); |
||||
|
} |
||||
|
} |
||||
|
END |
||||
|
# For Cygwin/MinGW, switch paths to Windows format before running javac and java |
||||
|
verbose " - Compiling Downloader.java ..." |
||||
|
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" |
||||
|
verbose " - Running Downloader.java ..." |
||||
|
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" |
||||
|
fi |
||||
|
|
||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file |
||||
|
if [ -n "${distributionSha256Sum-}" ]; then |
||||
|
distributionSha256Result=false |
||||
|
if [ "$MVN_CMD" = mvnd.sh ]; then |
||||
|
echo "Checksum validation is not supported for maven-mvnd." >&2 |
||||
|
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 |
||||
|
exit 1 |
||||
|
elif command -v sha256sum >/dev/null; then |
||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then |
||||
|
distributionSha256Result=true |
||||
|
fi |
||||
|
elif command -v shasum >/dev/null; then |
||||
|
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then |
||||
|
distributionSha256Result=true |
||||
|
fi |
||||
|
else |
||||
|
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 |
||||
|
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 |
||||
|
exit 1 |
||||
|
fi |
||||
|
if [ $distributionSha256Result = false ]; then |
||||
|
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 |
||||
|
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 |
||||
|
exit 1 |
||||
|
fi |
||||
|
fi |
||||
|
|
||||
|
# unzip and move |
||||
|
if command -v unzip >/dev/null; then |
||||
|
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" |
||||
|
else |
||||
|
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" |
||||
|
fi |
||||
|
|
||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name) |
||||
|
actualDistributionDir="" |
||||
|
|
||||
|
# First try the expected directory name (for regular distributions) |
||||
|
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then |
||||
|
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then |
||||
|
actualDistributionDir="$distributionUrlNameMain" |
||||
|
fi |
||||
|
fi |
||||
|
|
||||
|
# If not found, search for any directory with the Maven executable (for snapshots) |
||||
|
if [ -z "$actualDistributionDir" ]; then |
||||
|
# enable globbing to iterate over items |
||||
|
set +f |
||||
|
for dir in "$TMP_DOWNLOAD_DIR"/*; do |
||||
|
if [ -d "$dir" ]; then |
||||
|
if [ -f "$dir/bin/$MVN_CMD" ]; then |
||||
|
actualDistributionDir="$(basename "$dir")" |
||||
|
break |
||||
|
fi |
||||
|
fi |
||||
|
done |
||||
|
set -f |
||||
|
fi |
||||
|
|
||||
|
if [ -z "$actualDistributionDir" ]; then |
||||
|
verbose "Contents of $TMP_DOWNLOAD_DIR:" |
||||
|
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" |
||||
|
die "Could not find Maven distribution directory in extracted archive" |
||||
|
fi |
||||
|
|
||||
|
verbose "Found extracted Maven distribution directory: $actualDistributionDir" |
||||
|
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" |
||||
|
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" |
||||
|
|
||||
|
clean || : |
||||
|
exec_maven "$@" |
||||
@ -0,0 +1,189 @@ |
|||||
|
<# : batch portion |
||||
|
@REM ---------------------------------------------------------------------------- |
||||
|
@REM Licensed to the Apache Software Foundation (ASF) under one |
||||
|
@REM or more contributor license agreements. See the NOTICE file |
||||
|
@REM distributed with this work for additional information |
||||
|
@REM regarding copyright ownership. The ASF licenses this file |
||||
|
@REM to you under the Apache License, Version 2.0 (the |
||||
|
@REM "License"); you may not use this file except in compliance |
||||
|
@REM with the License. You may obtain a copy of the License at |
||||
|
@REM |
||||
|
@REM http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
@REM |
||||
|
@REM Unless required by applicable law or agreed to in writing, |
||||
|
@REM software distributed under the License is distributed on an |
||||
|
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
|
@REM KIND, either express or implied. See the License for the |
||||
|
@REM specific language governing permissions and limitations |
||||
|
@REM under the License. |
||||
|
@REM ---------------------------------------------------------------------------- |
||||
|
|
||||
|
@REM ---------------------------------------------------------------------------- |
||||
|
@REM Apache Maven Wrapper startup batch script, version 3.3.4 |
||||
|
@REM |
||||
|
@REM Optional ENV vars |
||||
|
@REM MVNW_REPOURL - repo url base for downloading maven distribution |
||||
|
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven |
||||
|
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output |
||||
|
@REM ---------------------------------------------------------------------------- |
||||
|
|
||||
|
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) |
||||
|
@SET __MVNW_CMD__= |
||||
|
@SET __MVNW_ERROR__= |
||||
|
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% |
||||
|
@SET PSModulePath= |
||||
|
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( |
||||
|
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) |
||||
|
) |
||||
|
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% |
||||
|
@SET __MVNW_PSMODULEP_SAVE= |
||||
|
@SET __MVNW_ARG0_NAME__= |
||||
|
@SET MVNW_USERNAME= |
||||
|
@SET MVNW_PASSWORD= |
||||
|
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) |
||||
|
@echo Cannot start maven from wrapper >&2 && exit /b 1 |
||||
|
@GOTO :EOF |
||||
|
: end batch / begin powershell #> |
||||
|
|
||||
|
$ErrorActionPreference = "Stop" |
||||
|
if ($env:MVNW_VERBOSE -eq "true") { |
||||
|
$VerbosePreference = "Continue" |
||||
|
} |
||||
|
|
||||
|
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties |
||||
|
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl |
||||
|
if (!$distributionUrl) { |
||||
|
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" |
||||
|
} |
||||
|
|
||||
|
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { |
||||
|
"maven-mvnd-*" { |
||||
|
$USE_MVND = $true |
||||
|
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" |
||||
|
$MVN_CMD = "mvnd.cmd" |
||||
|
break |
||||
|
} |
||||
|
default { |
||||
|
$USE_MVND = $false |
||||
|
$MVN_CMD = $script -replace '^mvnw','mvn' |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
# apply MVNW_REPOURL and calculate MAVEN_HOME |
||||
|
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> |
||||
|
if ($env:MVNW_REPOURL) { |
||||
|
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } |
||||
|
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" |
||||
|
} |
||||
|
$distributionUrlName = $distributionUrl -replace '^.*/','' |
||||
|
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' |
||||
|
|
||||
|
$MAVEN_M2_PATH = "$HOME/.m2" |
||||
|
if ($env:MAVEN_USER_HOME) { |
||||
|
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" |
||||
|
} |
||||
|
|
||||
|
if (-not (Test-Path -Path $MAVEN_M2_PATH)) { |
||||
|
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null |
||||
|
} |
||||
|
|
||||
|
$MAVEN_WRAPPER_DISTS = $null |
||||
|
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { |
||||
|
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" |
||||
|
} else { |
||||
|
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" |
||||
|
} |
||||
|
|
||||
|
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" |
||||
|
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' |
||||
|
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" |
||||
|
|
||||
|
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { |
||||
|
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" |
||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" |
||||
|
exit $? |
||||
|
} |
||||
|
|
||||
|
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { |
||||
|
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" |
||||
|
} |
||||
|
|
||||
|
# prepare tmp dir |
||||
|
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile |
||||
|
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" |
||||
|
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null |
||||
|
trap { |
||||
|
if ($TMP_DOWNLOAD_DIR.Exists) { |
||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } |
||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null |
||||
|
|
||||
|
# Download and Install Apache Maven |
||||
|
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." |
||||
|
Write-Verbose "Downloading from: $distributionUrl" |
||||
|
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" |
||||
|
|
||||
|
$webclient = New-Object System.Net.WebClient |
||||
|
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { |
||||
|
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) |
||||
|
} |
||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||
|
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null |
||||
|
|
||||
|
# If specified, validate the SHA-256 sum of the Maven distribution zip file |
||||
|
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum |
||||
|
if ($distributionSha256Sum) { |
||||
|
if ($USE_MVND) { |
||||
|
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." |
||||
|
} |
||||
|
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash |
||||
|
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { |
||||
|
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
# unzip and move |
||||
|
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null |
||||
|
|
||||
|
# Find the actual extracted directory name (handles snapshots where filename != directory name) |
||||
|
$actualDistributionDir = "" |
||||
|
|
||||
|
# First try the expected directory name (for regular distributions) |
||||
|
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" |
||||
|
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" |
||||
|
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { |
||||
|
$actualDistributionDir = $distributionUrlNameMain |
||||
|
} |
||||
|
|
||||
|
# If not found, search for any directory with the Maven executable (for snapshots) |
||||
|
if (!$actualDistributionDir) { |
||||
|
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { |
||||
|
$testPath = Join-Path $_.FullName "bin/$MVN_CMD" |
||||
|
if (Test-Path -Path $testPath -PathType Leaf) { |
||||
|
$actualDistributionDir = $_.Name |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!$actualDistributionDir) { |
||||
|
Write-Error "Could not find Maven distribution directory in extracted archive" |
||||
|
} |
||||
|
|
||||
|
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" |
||||
|
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null |
||||
|
try { |
||||
|
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null |
||||
|
} catch { |
||||
|
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { |
||||
|
Write-Error "fail to move MAVEN_HOME" |
||||
|
} |
||||
|
} finally { |
||||
|
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } |
||||
|
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } |
||||
|
} |
||||
|
|
||||
|
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" |
||||
@ -0,0 +1,97 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||
|
<version>3.5.15-SNAPSHOT</version> |
||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||
|
</parent> |
||||
|
<groupId>com.gcc_manpower</groupId> |
||||
|
<artifactId>gcc-manpower</artifactId> |
||||
|
<version>0.0.1-SNAPSHOT</version> |
||||
|
<name/> |
||||
|
<description/> |
||||
|
<url/> |
||||
|
<licenses> |
||||
|
<license/> |
||||
|
</licenses> |
||||
|
<developers> |
||||
|
<developer/> |
||||
|
</developers> |
||||
|
<scm> |
||||
|
<connection/> |
||||
|
<developerConnection/> |
||||
|
<tag/> |
||||
|
<url/> |
||||
|
</scm> |
||||
|
<properties> |
||||
|
<java.version>17</java.version> |
||||
|
</properties> |
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-jdbc</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.security</groupId> |
||||
|
<artifactId>spring-security-crypto</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-devtools</artifactId> |
||||
|
<scope>runtime</scope> |
||||
|
<optional>true</optional> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.mysql</groupId> |
||||
|
<artifactId>mysql-connector-j</artifactId> |
||||
|
<scope>runtime</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
<repositories> |
||||
|
<repository> |
||||
|
<id>spring-snapshots</id> |
||||
|
<name>Spring Snapshots</name> |
||||
|
<url>https://repo.spring.io/snapshot</url> |
||||
|
<releases> |
||||
|
<enabled>false</enabled> |
||||
|
</releases> |
||||
|
</repository> |
||||
|
</repositories> |
||||
|
<pluginRepositories> |
||||
|
<pluginRepository> |
||||
|
<id>spring-snapshots</id> |
||||
|
<name>Spring Snapshots</name> |
||||
|
<url>https://repo.spring.io/snapshot</url> |
||||
|
<releases> |
||||
|
<enabled>false</enabled> |
||||
|
</releases> |
||||
|
</pluginRepository> |
||||
|
</pluginRepositories> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
package com.gcc_manpower; |
||||
|
|
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
|
||||
|
@SpringBootApplication |
||||
|
public class GccManpowerApplication { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(GccManpowerApplication.class, args); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
package com.gcc_manpower.config; |
||||
|
|
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.jdbc.datasource.DriverManagerDataSource; |
||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement; |
||||
|
import javax.sql.DataSource; |
||||
|
|
||||
|
@Configuration |
||||
|
@EnableTransactionManagement |
||||
|
public class DataSourceConfig { |
||||
|
|
||||
|
private static String host = "localhost:3306"; |
||||
|
private static String dbpassword = "root"; |
||||
|
|
||||
|
// AWS |
||||
|
// private static String host = |
||||
|
// "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306"; |
||||
|
// private static String dbpassword = "gcc-facial-password"; |
||||
|
|
||||
|
@Bean(name = "mysqlManpowerDataSource") |
||||
|
public DataSource mysqlManpowerDataSource() { |
||||
|
DriverManagerDataSource dataSource = new DriverManagerDataSource(); |
||||
|
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); |
||||
|
dataSource.setUrl("jdbc:mysql://" + host + "/gcc_manpower"); |
||||
|
dataSource.setUsername("root"); |
||||
|
dataSource.setPassword(dbpassword); |
||||
|
return dataSource; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,559 @@ |
|||||
|
package com.gcc_manpower.controller; |
||||
|
|
||||
|
import com.gcc_manpower.service.AppUserService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Controller; |
||||
|
import org.springframework.ui.Model; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
import jakarta.servlet.http.HttpSession; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Controller |
||||
|
@RequestMapping("/") |
||||
|
public class HomeController { |
||||
|
|
||||
|
@Autowired |
||||
|
private AppUserService appUserService; |
||||
|
|
||||
|
private void addSummaryStats(Model model, List<Map<String, Object>> vendorDetails) { |
||||
|
int totalVendors = vendorDetails.size(); |
||||
|
int totalTenderCount = appUserService.getOverallTenderCount(); |
||||
|
int totalExistingCount = appUserService.getOverallActiveEmployeeCount(); |
||||
|
|
||||
|
int totalVacantCount = 0; |
||||
|
for (Map<String, Object> vendor : vendorDetails) { |
||||
|
List<Map<String, Object>> designations = (List<Map<String, Object>>) vendor.get("designationDetails"); |
||||
|
if (designations != null) { |
||||
|
for (Map<String, Object> des : designations) { |
||||
|
Object vacantObj = des.get("vacant_count"); |
||||
|
if (vacantObj != null) { |
||||
|
try { |
||||
|
totalVacantCount += Integer.parseInt(String.valueOf(vacantObj).trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
model.addAttribute("totalVendors", totalVendors); |
||||
|
model.addAttribute("totalTenderCount", totalTenderCount); |
||||
|
model.addAttribute("totalExistingCount", totalExistingCount); |
||||
|
model.addAttribute("totalVacantCount", totalVacantCount); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/") |
||||
|
public String home(Model model, HttpSession session) { |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
if (usrName == null) { |
||||
|
return "redirect:/login"; |
||||
|
} |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
List<Map<String, Object>> vendorDetails = appUserService.getVendorDetailsWithCounts(); |
||||
|
addSummaryStats(model, vendorDetails); |
||||
|
|
||||
|
model.addAttribute("curPage", "home"); |
||||
|
model.addAttribute("isAdmin", isAdmin); |
||||
|
model.addAttribute("urls", new ArrayList<>()); // default empty services |
||||
|
model.addAttribute("vendorDetails", vendorDetails); |
||||
|
model.addAttribute("locations", appUserService.getActiveLocations()); |
||||
|
model.addAttribute("designations", appUserService.getActiveDesignations()); |
||||
|
model.addAttribute("userTypes", appUserService.getActiveUserTypes()); |
||||
|
return "home/home"; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/login") |
||||
|
public String login(HttpSession session) { |
||||
|
// If already logged in, redirect to the home page |
||||
|
if (session.getAttribute("usrName") != null) { |
||||
|
return "redirect:/"; |
||||
|
} |
||||
|
return "home/login"; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/val-app-usr-login") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> validateAppUserLogin( |
||||
|
@RequestParam("usrName") String usrName, |
||||
|
@RequestParam("usrPass") String usrPass, |
||||
|
HttpSession session) { |
||||
|
|
||||
|
Map<String, Object> authResult = appUserService.authenticate(usrName, usrPass); |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
|
||||
|
if (Boolean.TRUE.equals(authResult.get("success"))) { |
||||
|
session.setAttribute("usrName", authResult.get("usrName")); |
||||
|
session.setAttribute("usrRole", authResult.get("usrRole")); |
||||
|
session.setMaxInactiveInterval(600); // 10 minutes session timeout |
||||
|
response.put("result", true); |
||||
|
response.put("usrName", authResult.get("usrName")); |
||||
|
} else { |
||||
|
response.put("result", false); |
||||
|
response.put("error", authResult.get("error")); |
||||
|
} |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/get-role") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> getRole(HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
|
||||
|
if (usrName == null) { |
||||
|
response.put("authenticated", false); |
||||
|
response.put("isAdmin", false); |
||||
|
} else { |
||||
|
response.put("authenticated", true); |
||||
|
response.put("isAdmin", "ADMIN".equalsIgnoreCase(usrRole)); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/redirect") |
||||
|
public String redirect() { |
||||
|
return "redirect:/"; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/view-login-det") |
||||
|
public String viewLoginDetails(Model model, HttpSession session) { |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
|
||||
|
if (usrName == null) { |
||||
|
return "redirect:/login"; |
||||
|
} |
||||
|
|
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
// Fetch exact spreadsheet rows dynamically from MySQL using JdbcTemplate |
||||
|
List<Map<String, Object>> manpowerEntries = appUserService.getManpowerTenders(); |
||||
|
List<Map<String, Object>> vendorDetails = appUserService.getVendorDetailsWithCounts(); |
||||
|
|
||||
|
model.addAttribute("curPage", "view-login-det"); |
||||
|
model.addAttribute("isAdmin", isAdmin); |
||||
|
model.addAttribute("manpowerEntries", manpowerEntries); |
||||
|
model.addAttribute("vendorDetails", vendorDetails); |
||||
|
model.addAttribute("designations", appUserService.getActiveDesignations()); |
||||
|
return "home/view-login-details"; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/vendor-employees") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getVendorEmployees( |
||||
|
@RequestParam("vendorName") String vendorName) { |
||||
|
return appUserService.getEmployeesByVendor(vendorName); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/employee/history") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getEmployeeHistory( |
||||
|
@RequestParam("empCode") String empCode) { |
||||
|
return appUserService.getEmployeeHistory(empCode); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/all-employees") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getAllEmployees() { |
||||
|
return appUserService.getAllEmployees(); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/save-manpower-stats") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> saveManpowerStats( |
||||
|
@RequestBody List<Map<String, Object>> payload, |
||||
|
HttpSession session) { |
||||
|
|
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
|
||||
|
if (usrName == null) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized access! Please log in."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
boolean overallSuccess = true; |
||||
|
for (Map<String, Object> row : payload) { |
||||
|
int id = ((Number) row.get("id")).intValue(); |
||||
|
int availableCount = ((Number) row.get("availableCount")).intValue(); |
||||
|
String vacantRemarks = (String) row.get("vacantRemarks"); |
||||
|
|
||||
|
boolean rowSuccess = appUserService.updateManpowerTender(id, availableCount, vacantRemarks); |
||||
|
if (!rowSuccess) { |
||||
|
overallSuccess = false; |
||||
|
} |
||||
|
} |
||||
|
response.put("success", overallSuccess); |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to save statistics: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/employee") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> getEmployeeByCode( |
||||
|
@RequestParam("empCode") String empCode, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
if (usrName == null) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized access! Please log in."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
Map<String, Object> emp = appUserService.getEmployeeByCode(empCode); |
||||
|
if (emp != null) { |
||||
|
response.put("success", true); |
||||
|
response.put("employee", emp); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Employee not found or inactive."); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/employee/add") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> addEmployee( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
boolean success = appUserService.addEmployee(payload); |
||||
|
if (success) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Employee registered successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to register employee. The employee code might already exist."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/employee/update") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> updateEmployee( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
boolean success = appUserService.updateEmployee(payload); |
||||
|
if (success) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Employee updated successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to update employee details. Record not found or inactive."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/vendor/add-work-order") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> addVendorWorkOrder( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
String vendorType = (String) payload.get("vendorType"); |
||||
|
String vendorIdStr = (String) payload.get("vendorId"); |
||||
|
String vendorName = (String) payload.get("vendorName"); |
||||
|
String description = (String) payload.get("description"); |
||||
|
String contractorName = (String) payload.get("contractorName"); |
||||
|
String fileNo = (String) payload.get("fileNo"); |
||||
|
String subject = (String) payload.get("subject"); |
||||
|
String issueDate = (String) payload.get("issueDate"); |
||||
|
String endDate = (String) payload.get("endDate"); |
||||
|
String totalEmployees = (String) payload.get("totalEmployees"); |
||||
|
|
||||
|
Integer orderby = null; |
||||
|
if (payload.get("orderby") != null) { |
||||
|
try { |
||||
|
orderby = ((Number) payload.get("orderby")).intValue(); |
||||
|
} catch (Exception ignored) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Convert to uppercase for Contractor Name and File No |
||||
|
if (contractorName != null) { |
||||
|
contractorName = contractorName.toUpperCase(); |
||||
|
} |
||||
|
if (fileNo != null) { |
||||
|
fileNo = fileNo.toUpperCase(); |
||||
|
} |
||||
|
|
||||
|
long finalVendorId = 0; |
||||
|
if ("new".equalsIgnoreCase(vendorType)) { |
||||
|
// Check if vendor name already exists |
||||
|
List<Map<String, Object>> existingVendors = appUserService.getVendorDetailsWithCounts(); |
||||
|
boolean vendorExists = false; |
||||
|
for (Map<String, Object> v : existingVendors) { |
||||
|
String vName = (String) v.get("vendor_name"); |
||||
|
if (vName != null && vName.equalsIgnoreCase(vendorName)) { |
||||
|
vendorExists = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
if (vendorExists) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "A vendor with this name already exists."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
// Insert into vendor_details |
||||
|
finalVendorId = appUserService.addVendor(vendorName, description, totalEmployees, orderby); |
||||
|
if (finalVendorId == 0) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to create new vendor details."); |
||||
|
return response; |
||||
|
} |
||||
|
} else { |
||||
|
if (vendorIdStr == null || vendorIdStr.trim().isEmpty()) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "No existing vendor selected."); |
||||
|
return response; |
||||
|
} |
||||
|
try { |
||||
|
finalVendorId = Long.parseLong(vendorIdStr); |
||||
|
} catch (NumberFormatException e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Invalid vendor ID formatting."); |
||||
|
return response; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Insert into vendor_details_work_order |
||||
|
boolean success = appUserService.addVendorWorkOrder( |
||||
|
String.valueOf(finalVendorId), |
||||
|
description, |
||||
|
contractorName, |
||||
|
fileNo, |
||||
|
subject, |
||||
|
issueDate, |
||||
|
endDate, |
||||
|
totalEmployees, |
||||
|
orderby); |
||||
|
|
||||
|
if (success) { |
||||
|
// If it's an existing vendor, add total_employees to vendor_details |
||||
|
if (!"new".equalsIgnoreCase(vendorType)) { |
||||
|
int addedEmployees = 0; |
||||
|
if (totalEmployees != null) { |
||||
|
try { |
||||
|
addedEmployees = Integer.parseInt(totalEmployees.trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
if (addedEmployees > 0) { |
||||
|
appUserService.updateVendorTotalEmployees(String.valueOf(finalVendorId), addedEmployees); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
response.put("success", true); |
||||
|
response.put("message", "Vendor work order saved successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to register vendor work order."); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/employee/delete") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> deleteEmployee( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
String empCode = (String) payload.get("empCode"); |
||||
|
if (empCode == null || empCode.trim().isEmpty()) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Employee code is required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
boolean success = appUserService.deleteEmployee(empCode); |
||||
|
if (success) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Employee deleted successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to delete employee or record not found."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/vendor/delete") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> deleteVendor( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
Object idObj = payload.get("id"); |
||||
|
if (idObj == null) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Vendor ID is required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
String idStr = String.valueOf(idObj); |
||||
|
boolean success = appUserService.deleteVendor(idStr); |
||||
|
if (success) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Vendor and associated records deleted successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to delete vendor or record not found."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/vendor-employees-all") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getVendorEmployeesAll( |
||||
|
@RequestParam("vendorIdOrName") String vendorIdOrName) { |
||||
|
return appUserService.getEmployeesByVendorAll(vendorIdOrName); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/api/employee/restore") |
||||
|
@ResponseBody |
||||
|
public Map<String, Object> restoreEmployee( |
||||
|
@RequestBody Map<String, Object> payload, |
||||
|
HttpSession session) { |
||||
|
Map<String, Object> response = new HashMap<>(); |
||||
|
String usrName = (String) session.getAttribute("usrName"); |
||||
|
String usrRole = (String) session.getAttribute("usrRole"); |
||||
|
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); |
||||
|
|
||||
|
if (usrName == null || !isAdmin) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Unauthorized! Admin access required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
String empCode = (String) payload.get("empCode"); |
||||
|
if (empCode == null || empCode.trim().isEmpty()) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Employee code is required."); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
boolean success = appUserService.restoreEmployee(empCode); |
||||
|
if (success) { |
||||
|
response.put("success", true); |
||||
|
response.put("message", "Employee restored successfully!"); |
||||
|
} else { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Failed to restore employee or record not found."); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
response.put("success", false); |
||||
|
response.put("error", "Error: " + e.getMessage()); |
||||
|
} |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/api/all-employees-with-deleted") |
||||
|
@ResponseBody |
||||
|
public List<Map<String, Object>> getAllEmployeesWithDeleted() { |
||||
|
return appUserService.getAllEmployeesWithDeleted(); |
||||
|
} |
||||
|
@PostMapping("/usr-logout") |
||||
|
public String logout(HttpSession session) { |
||||
|
session.invalidate(); |
||||
|
return "redirect:/login"; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,836 @@ |
|||||
|
package com.gcc_manpower.service; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||
|
import org.springframework.jdbc.core.JdbcTemplate; |
||||
|
import org.springframework.jdbc.support.GeneratedKeyHolder; |
||||
|
import org.springframework.jdbc.support.KeyHolder; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import javax.sql.DataSource; |
||||
|
import jakarta.annotation.PostConstruct; |
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.Statement; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Service |
||||
|
public class AppUserService { |
||||
|
|
||||
|
private JdbcTemplate jdbcManpowerTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
public void setDataSource(@Qualifier("mysqlManpowerDataSource") DataSource manpowerDataSource) { |
||||
|
this.jdbcManpowerTemplate = new JdbcTemplate(manpowerDataSource); |
||||
|
} |
||||
|
|
||||
|
public Map<String, Object> authenticate(String username, String password) { |
||||
|
Map<String, Object> result = new HashMap<>(); |
||||
|
|
||||
|
String sql = "SELECT usr_name, usr_pass, usr_role, is_active FROM app_user WHERE usr_name = ?"; |
||||
|
|
||||
|
try { |
||||
|
List<Map<String, Object>> users = jdbcManpowerTemplate.queryForList(sql, username); |
||||
|
|
||||
|
if (users == null || users.isEmpty()) { |
||||
|
result.put("success", false); |
||||
|
result.put("error", "Invalid username or password!"); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
Map<String, Object> user = users.get(0); |
||||
|
|
||||
|
// Check if user is active |
||||
|
int isActive = 1; |
||||
|
Object activeObj = user.get("is_active"); |
||||
|
if (activeObj instanceof Number) { |
||||
|
isActive = ((Number) activeObj).intValue(); |
||||
|
} else if (activeObj instanceof Boolean) { |
||||
|
isActive = (Boolean) activeObj ? 1 : 0; |
||||
|
} |
||||
|
|
||||
|
if (isActive == 0) { |
||||
|
result.put("success", false); |
||||
|
result.put("error", "User account is inactive!"); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
String dbPass = (String) user.get("usr_pass"); |
||||
|
boolean matches = verifyPassword(password, dbPass); |
||||
|
|
||||
|
if (matches) { |
||||
|
result.put("success", true); |
||||
|
result.put("usrName", user.get("usr_name")); |
||||
|
result.put("usrRole", user.get("usr_role")); |
||||
|
} else { |
||||
|
result.put("success", false); |
||||
|
result.put("error", "Invalid username or password!"); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
result.put("success", false); |
||||
|
result.put("error", "Database connection/query error: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getManpowerTenders() { |
||||
|
String sql = "SELECT id, description, vendor_name, tender_count, available_count, working_location, registered_count, vacant_remarks FROM manpower_tender ORDER BY id"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean updateManpowerTender(int id, int availableCount, String vacantRemarks) { |
||||
|
String sql = "UPDATE manpower_tender SET available_count = ?, vacant_remarks = ? WHERE id = ?"; |
||||
|
try { |
||||
|
int rows = jdbcManpowerTemplate.update(sql, availableCount, vacantRemarks, id); |
||||
|
return rows > 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error updating manpower tender id " + id + ": " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getDesignationsForVendor(String vendorId) { |
||||
|
String sql = "SELECT " + |
||||
|
" des.id, " + |
||||
|
" des.designation, " + |
||||
|
" des.available_manpower, " + |
||||
|
" des.vacant_count, " + |
||||
|
" des.tender_manpower_designation_wise, " + |
||||
|
" COALESCE(des.File_no, '-') AS File_no, " + |
||||
|
" COALESCE(des.work_order_issue_date, '-') AS work_order_issue_date, " + |
||||
|
" COALESCE(des.work_order_end_date, '-') AS work_order_end_date " + |
||||
|
"FROM employee_workorder_details des " + |
||||
|
"WHERE des.vid = ? AND des.isactive = 1 AND des.isdelete = 0 " + |
||||
|
"ORDER BY des.orderby, des.designation"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql, vendorId); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching designations for vendor ID " + vendorId + ": " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getVendorDetailsWithCounts() { |
||||
|
String sql = "SELECT id, vendor_name, description, total_employees, orderby, cdate, isactive, isdelete, " + |
||||
|
"(SELECT COUNT(*) FROM employee_details e WHERE (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) AND e.isactive = 1 AND e.isdelete = 0) AS actual_employee_count " |
||||
|
+ |
||||
|
"FROM vendor_details v " + |
||||
|
"WHERE v.isactive = 1 AND v.isdelete = 0 " + |
||||
|
"ORDER BY v.orderby, v.vendor_name"; |
||||
|
try { |
||||
|
List<Map<String, Object>> vendors = jdbcManpowerTemplate.queryForList(sql); |
||||
|
if (vendors != null) { |
||||
|
for (Map<String, Object> vendor : vendors) { |
||||
|
String vendorId = String.valueOf(vendor.get("id")); |
||||
|
List<Map<String, Object>> designations = getDesignationsForVendor(vendorId); |
||||
|
vendor.put("designationDetails", designations); |
||||
|
} |
||||
|
} |
||||
|
return vendors; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching vendor details with counts: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
public int getOverallTenderCount() { |
||||
|
String sql = "SELECT SUM(total_employees) FROM vendor_details WHERE isactive = 1 AND isdelete = 0"; |
||||
|
try { |
||||
|
Integer sum = jdbcManpowerTemplate.queryForObject(sql, Integer.class); |
||||
|
return sum != null ? sum : 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching overall tender count: " + e.getMessage()); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int getOverallActiveEmployeeCount() { |
||||
|
String sql = "SELECT COUNT(*) FROM employee_details WHERE isactive = 1 AND isdelete = 0"; |
||||
|
try { |
||||
|
Integer count = jdbcManpowerTemplate.queryForObject(sql, Integer.class); |
||||
|
return count != null ? count : 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching overall active employee count: " + e.getMessage()); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getActiveLocations() { |
||||
|
String sql = "SELECT id, location_name FROM employee_location_details WHERE isactive = 1 AND isdelete = 0 " + |
||||
|
"ORDER BY " + |
||||
|
" CASE WHEN location_name LIKE 'zone_%' THEN 0 ELSE 1 END, " + |
||||
|
" CASE WHEN location_name LIKE 'zone_%' THEN CAST(SUBSTRING(location_name, 6) AS UNSIGNED) ELSE 0 END, " |
||||
|
+ |
||||
|
" orderby, " + |
||||
|
" location_name"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching active locations: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getActiveDesignations() { |
||||
|
String sql = "SELECT id, designation, File_no FROM employee_workorder_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, designation"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching active designations: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getActiveUserTypes() { |
||||
|
String sql = "SELECT id, usertype FROM user_type_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, usertype"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching active user types: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getEmployeesByVendor(String vendorName) { |
||||
|
String vendorId = null; |
||||
|
try { |
||||
|
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList( |
||||
|
"SELECT id FROM vendor_details WHERE vendor_name = ? AND isactive = 1 AND isdelete = 0", |
||||
|
vendorName); |
||||
|
if (vendorList != null && !vendorList.isEmpty()) { |
||||
|
vendorId = String.valueOf(vendorList.get(0).get("id")); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching vendor ID by name: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
String sql = "SELECT " + |
||||
|
" e.id, " + |
||||
|
" e.emp_code, " + |
||||
|
" e.name, " + |
||||
|
" e.email, " + |
||||
|
" e.mobile, " + |
||||
|
" e.vendor_id, " + |
||||
|
" e.isactive, " + |
||||
|
" e.isdelete, " + |
||||
|
" e.joined_date, " + |
||||
|
" e.cdate, " + |
||||
|
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + |
||||
|
" COALESCE(ut.usertype, e.user_type) AS user_type, " + |
||||
|
" COALESCE(des.designation, e.designation) AS designation, " + |
||||
|
" COALESCE(loc.location_name, e.location) AS location, " + |
||||
|
" COALESCE(des.File_no, '-') AS work_order, " + |
||||
|
" COALESCE(des.work_order_issue_date, '-') AS issue_date, " + |
||||
|
" COALESCE(des.work_order_end_date, '-') AS end_date " + |
||||
|
"FROM employee_details e " + |
||||
|
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " + |
||||
|
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + |
||||
|
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) " |
||||
|
+ |
||||
|
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) " |
||||
|
+ |
||||
|
"WHERE (e.vendor_id = ? OR e.vendor_id = ?) AND e.isactive = 1 AND e.isdelete = 0 " + |
||||
|
"ORDER BY e.name"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql, vendorId != null ? vendorId : "", vendorName); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching employees by vendor " + vendorName + ": " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getAllEmployees() { |
||||
|
String sql = "SELECT " + |
||||
|
" e.id, " + |
||||
|
" e.emp_code, " + |
||||
|
" e.name, " + |
||||
|
" e.email, " + |
||||
|
" e.mobile, " + |
||||
|
" e.vendor_id, " + |
||||
|
" e.isactive, " + |
||||
|
" e.isdelete, " + |
||||
|
" e.joined_date, " + |
||||
|
" e.cdate, " + |
||||
|
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + |
||||
|
" COALESCE(ut.usertype, e.user_type) AS user_type, " + |
||||
|
" COALESCE(des.designation, e.designation) AS designation, " + |
||||
|
" COALESCE(loc.location_name, e.location) AS location, " + |
||||
|
" COALESCE(des.File_no, '-') AS work_order, " + |
||||
|
" COALESCE(des.work_order_issue_date, '-') AS issue_date, " + |
||||
|
" COALESCE(des.work_order_end_date, '-') AS end_date " + |
||||
|
"FROM employee_details e " + |
||||
|
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " + |
||||
|
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + |
||||
|
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) " |
||||
|
+ |
||||
|
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) " |
||||
|
+ |
||||
|
"WHERE e.isactive = 1 AND e.isdelete = 0 " + |
||||
|
"ORDER BY e.name"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching all employees: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Map<String, Object> getEmployeeByCode(String empCode) { |
||||
|
String sql = "SELECT id, emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, isactive, isdelete " |
||||
|
+ |
||||
|
"FROM employee_details " + |
||||
|
"WHERE emp_code = ? AND isdelete = 0"; |
||||
|
try { |
||||
|
List<Map<String, Object>> employees = jdbcManpowerTemplate.queryForList(sql, empCode); |
||||
|
if (employees != null && !employees.isEmpty()) { |
||||
|
return employees.get(0); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching employee by code " + empCode + ": " + e.getMessage()); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public boolean addEmployee(Map<String, Object> emp) { |
||||
|
String checkSql = "SELECT COUNT(*) FROM employee_details WHERE emp_code = ? AND isdelete = 0"; |
||||
|
try { |
||||
|
Integer count = jdbcManpowerTemplate.queryForObject(checkSql, Integer.class, emp.get("emp_code")); |
||||
|
if (count != null && count > 0) { |
||||
|
return false; |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error checking duplicate employee code: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
// Check designation limit |
||||
|
String designationIdOrName = String.valueOf(emp.get("designation")); |
||||
|
String vendorId = String.valueOf(emp.get("vendor_id")); |
||||
|
|
||||
|
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " |
||||
|
+ |
||||
|
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0"; |
||||
|
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId, designationIdOrName, |
||||
|
designationIdOrName); |
||||
|
if (rows != null && !rows.isEmpty()) { |
||||
|
for (Map<String, Object> row : rows) { |
||||
|
String desId = String.valueOf(row.get("id")); |
||||
|
String desName = (String) row.get("designation"); |
||||
|
String tenderStr = (String) row.get("tender_manpower_designation_wise"); |
||||
|
|
||||
|
// Fetch the count of active, non-deleted employees for this designation/vendor |
||||
|
String countSql = "SELECT COUNT(*) FROM employee_details e " + |
||||
|
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " |
||||
|
+ |
||||
|
" AND (e.designation = ? OR e.designation = ?) " + |
||||
|
" AND e.isactive = 1 AND e.isdelete = 0"; |
||||
|
|
||||
|
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId, |
||||
|
desId, desName); |
||||
|
int currentActive = activeCount != null ? activeCount : 0; |
||||
|
|
||||
|
int tenderLimit = 0; |
||||
|
if (tenderStr != null) { |
||||
|
try { |
||||
|
tenderLimit = Integer.parseInt(tenderStr.trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (currentActive >= tenderLimit) { |
||||
|
throw new RuntimeException("Manpower limit reached for designation '" + desName |
||||
|
+ "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
String sql = "INSERT INTO employee_details (emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, cdate, isactive, isdelete) " |
||||
|
+ |
||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)"; |
||||
|
try { |
||||
|
int updateRows = jdbcManpowerTemplate.update(sql, |
||||
|
emp.get("emp_code"), |
||||
|
emp.get("name"), |
||||
|
emp.get("email"), |
||||
|
emp.get("mobile"), |
||||
|
emp.get("user_type"), |
||||
|
emp.get("designation"), |
||||
|
emp.get("location"), |
||||
|
emp.get("vendor_id"), |
||||
|
emp.get("joined_date")); |
||||
|
if (updateRows > 0) { |
||||
|
recalculateDesignationManpower(String.valueOf(emp.get("designation")), |
||||
|
String.valueOf(emp.get("vendor_id"))); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error adding employee: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean updateEmployee(Map<String, Object> emp) { |
||||
|
String empCode = (String) emp.get("emp_code"); |
||||
|
String selectSql = "SELECT emp_code, name, email, mobile, user_type, designation, location, vendor_id, isactive, isdelete " |
||||
|
+ |
||||
|
"FROM employee_details WHERE emp_code = ? AND isdelete = 0"; |
||||
|
List<Map<String, Object>> oldList; |
||||
|
try { |
||||
|
oldList = jdbcManpowerTemplate.queryForList(selectSql, empCode); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error retrieving old employee details: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
if (oldList == null || oldList.isEmpty()) { |
||||
|
return false; |
||||
|
} |
||||
|
Map<String, Object> old = oldList.get(0); |
||||
|
|
||||
|
// If designation or vendor_id is changing, check limit on the target |
||||
|
// designation/vendor |
||||
|
String oldDes = String.valueOf(old.get("designation")); |
||||
|
String oldVendor = String.valueOf(old.get("vendor_id")); |
||||
|
String newDes = String.valueOf(emp.get("designation")); |
||||
|
String newVendor = String.valueOf(emp.get("vendor_id")); |
||||
|
|
||||
|
if (!oldDes.equals(newDes) || !oldVendor.equals(newVendor)) { |
||||
|
// Check designation limit for new designation & vendor |
||||
|
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " |
||||
|
+ |
||||
|
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0"; |
||||
|
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, newVendor, newDes, newDes); |
||||
|
if (rows != null && !rows.isEmpty()) { |
||||
|
for (Map<String, Object> row : rows) { |
||||
|
String desId = String.valueOf(row.get("id")); |
||||
|
String desName = (String) row.get("designation"); |
||||
|
String tenderStr = (String) row.get("tender_manpower_designation_wise"); |
||||
|
|
||||
|
String countSql = "SELECT COUNT(*) FROM employee_details e " + |
||||
|
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " |
||||
|
+ |
||||
|
" AND (e.designation = ? OR e.designation = ?) " + |
||||
|
" AND e.isactive = 1 AND e.isdelete = 0"; |
||||
|
|
||||
|
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, newVendor, |
||||
|
newVendor, desId, desName); |
||||
|
int currentActive = activeCount != null ? activeCount : 0; |
||||
|
|
||||
|
int tenderLimit = 0; |
||||
|
if (tenderStr != null) { |
||||
|
try { |
||||
|
tenderLimit = Integer.parseInt(tenderStr.trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (currentActive >= tenderLimit) { |
||||
|
throw new RuntimeException("Manpower limit reached for designation '" + desName |
||||
|
+ "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
String historySql = "INSERT INTO employee_details_update_history (emp_code, name, email, mobile, user_type, designation, location, vendor_id, updated_date, isactive, isdelete) " |
||||
|
+ |
||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?)"; |
||||
|
try { |
||||
|
jdbcManpowerTemplate.update(historySql, |
||||
|
old.get("emp_code"), |
||||
|
old.get("name"), |
||||
|
old.get("email"), |
||||
|
old.get("mobile"), |
||||
|
old.get("user_type"), |
||||
|
old.get("designation"), |
||||
|
old.get("location"), |
||||
|
old.get("vendor_id"), |
||||
|
old.get("isactive"), |
||||
|
old.get("isdelete")); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error inserting history record: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
String updateSql = "UPDATE employee_details SET name = ?, email = ?, mobile = ?, user_type = ?, designation = ?, location = ?, vendor_id = ?, joined_date = ? " |
||||
|
+ |
||||
|
"WHERE emp_code = ? AND isdelete = 0"; |
||||
|
try { |
||||
|
int rows = jdbcManpowerTemplate.update(updateSql, |
||||
|
emp.get("name"), |
||||
|
emp.get("email"), |
||||
|
emp.get("mobile"), |
||||
|
emp.get("user_type"), |
||||
|
emp.get("designation"), |
||||
|
emp.get("location"), |
||||
|
emp.get("vendor_id"), |
||||
|
emp.get("joined_date"), |
||||
|
empCode); |
||||
|
if (rows > 0) { |
||||
|
recalculateDesignationManpower(String.valueOf(old.get("designation")), |
||||
|
String.valueOf(old.get("vendor_id"))); |
||||
|
recalculateDesignationManpower(String.valueOf(emp.get("designation")), |
||||
|
String.valueOf(emp.get("vendor_id"))); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error updating employee record: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getEmployeeHistory(String empCode) { |
||||
|
String sql = "SELECT " + |
||||
|
" h.emp_code, " + |
||||
|
" h.name, " + |
||||
|
" h.email, " + |
||||
|
" h.mobile, " + |
||||
|
" h.updated_date, " + |
||||
|
" COALESCE(v.vendor_name, h.vendor_id) AS vendor_name, " + |
||||
|
" COALESCE(ut.usertype, h.user_type) AS user_type, " + |
||||
|
" COALESCE(des.designation, h.designation) AS designation, " + |
||||
|
" COALESCE(loc.location_name, h.location) AS location " + |
||||
|
"FROM employee_details_update_history h " + |
||||
|
"LEFT JOIN vendor_details v ON (h.vendor_id = CAST(v.id AS CHAR) OR h.vendor_id = v.vendor_name) " + |
||||
|
"LEFT JOIN user_type_details ut ON (h.user_type = CAST(ut.id AS CHAR) OR h.user_type = ut.usertype) " + |
||||
|
"LEFT JOIN employee_workorder_details des ON (h.designation = CAST(des.id AS CHAR) OR h.designation = des.designation) " + |
||||
|
"LEFT JOIN employee_location_details loc ON (h.location = CAST(loc.id AS CHAR) OR h.location = loc.location_name) " + |
||||
|
"WHERE h.emp_code = ? " + |
||||
|
"ORDER BY h.updated_date DESC"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql, empCode); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching employee history: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean verifyPassword(String rawPassword, String dbPassword) { |
||||
|
if (dbPassword != null && dbPassword.startsWith("$2a$")) { |
||||
|
try { |
||||
|
// Dynamically load BCryptPasswordEncoder using reflection to prevent class |
||||
|
// loading errors |
||||
|
Class<?> clazz = Class.forName("org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"); |
||||
|
Object encoder = clazz.getDeclaredConstructor().newInstance(); |
||||
|
java.lang.reflect.Method method = clazz.getMethod("matches", CharSequence.class, String.class); |
||||
|
return (Boolean) method.invoke(encoder, rawPassword, dbPassword); |
||||
|
} catch (ClassNotFoundException e) { |
||||
|
System.err.println( |
||||
|
"WARNING: BCryptPasswordEncoder class not found on active classpath! Falling back to plain text match."); |
||||
|
return rawPassword.equals(dbPassword); |
||||
|
} catch (Exception e) { |
||||
|
return rawPassword.equals(dbPassword); |
||||
|
} |
||||
|
} |
||||
|
return rawPassword != null && rawPassword.equals(dbPassword); |
||||
|
} |
||||
|
|
||||
|
public long addVendor(String vendorName, String description, String totalEmployees, Integer orderby) { |
||||
|
if (orderby == null) { |
||||
|
try { |
||||
|
Integer maxOrder = jdbcManpowerTemplate.queryForObject("SELECT MAX(orderby) FROM vendor_details", |
||||
|
Integer.class); |
||||
|
orderby = (maxOrder != null) ? maxOrder + 1 : 1; |
||||
|
} catch (Exception e) { |
||||
|
orderby = 1; |
||||
|
} |
||||
|
} |
||||
|
final int finalOrderby = orderby; |
||||
|
KeyHolder keyHolder = new GeneratedKeyHolder(); |
||||
|
try { |
||||
|
jdbcManpowerTemplate.update(connection -> { |
||||
|
PreparedStatement ps = connection.prepareStatement( |
||||
|
"INSERT INTO vendor_details (vendor_name, description, total_employees, orderby, cdate, isactive, isdelete) " |
||||
|
+ |
||||
|
"VALUES (?, ?, ?, ?, NOW(), 1, 0)", |
||||
|
Statement.RETURN_GENERATED_KEYS); |
||||
|
ps.setString(1, vendorName); |
||||
|
ps.setString(2, description); |
||||
|
ps.setString(3, totalEmployees); |
||||
|
ps.setInt(4, finalOrderby); |
||||
|
return ps; |
||||
|
}, keyHolder); |
||||
|
Number key = keyHolder.getKey(); |
||||
|
return key != null ? key.longValue() : 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error adding vendor to vendor_details: " + e.getMessage()); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean addVendorWorkOrder(String vendorId, String description, String contractorName, String fileNo, |
||||
|
String subject, String issueDate, String endDate, String totalEmployees, Integer orderby) { |
||||
|
if (orderby == null) { |
||||
|
try { |
||||
|
Integer maxOrder = jdbcManpowerTemplate |
||||
|
.queryForObject("SELECT MAX(orderby) FROM employee_workorder_details", Integer.class); |
||||
|
orderby = (maxOrder != null) ? maxOrder + 1 : 1; |
||||
|
} catch (Exception e) { |
||||
|
orderby = 1; |
||||
|
} |
||||
|
} |
||||
|
String sql = "INSERT INTO employee_workorder_details (designation, vid, tender_manpower_designation_wise, available_manpower, vacant_count, contract_name, File_no, subject, work_order_issue_date, work_order_end_date, orderby, cdate, isactive, isdelete) " |
||||
|
+ |
||||
|
"VALUES (?, ?, ?, '0', ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)"; |
||||
|
try { |
||||
|
int rows = jdbcManpowerTemplate.update(sql, |
||||
|
subject, // designation |
||||
|
vendorId, // vid |
||||
|
totalEmployees, // tender_manpower_designation_wise |
||||
|
totalEmployees, // vacant_count |
||||
|
contractorName, // contract_name |
||||
|
fileNo, // File_no |
||||
|
subject, // subject |
||||
|
issueDate, // work_order_issue_date |
||||
|
endDate, // work_order_end_date |
||||
|
orderby // orderby |
||||
|
); |
||||
|
if (rows > 0) { |
||||
|
// Recalculate designation manpower (handles available_manpower and |
||||
|
// vacant_count) |
||||
|
recalculateDesignationManpower(subject, vendorId); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error adding work order to employee_workorder_details: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean updateVendorTotalEmployees(String vendorId, int addedEmployees) { |
||||
|
String selectSql = "SELECT total_employees FROM vendor_details WHERE id = ?"; |
||||
|
try { |
||||
|
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(selectSql, vendorId); |
||||
|
if (rows == null || rows.isEmpty()) { |
||||
|
return false; |
||||
|
} |
||||
|
Object rawTotal = rows.get(0).get("total_employees"); |
||||
|
String currentTotalStr = rawTotal != null ? String.valueOf(rawTotal) : "0"; |
||||
|
int currentTotal = 0; |
||||
|
if (currentTotalStr != null) { |
||||
|
try { |
||||
|
currentTotal = Integer.parseInt(currentTotalStr.trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
int newTotal = currentTotal + addedEmployees; |
||||
|
String updateSql = "UPDATE vendor_details SET total_employees = ? WHERE id = ?"; |
||||
|
int updateRows = jdbcManpowerTemplate.update(updateSql, String.valueOf(newTotal), vendorId); |
||||
|
return updateRows > 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error updating total employees for vendor ID " + vendorId + ": " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void recalculateDesignationManpower(String designationIdOrName, String vendorId) { |
||||
|
if (designationIdOrName == null || designationIdOrName.trim().isEmpty() || vendorId == null |
||||
|
|| vendorId.trim().isEmpty()) { |
||||
|
return; |
||||
|
} |
||||
|
try { |
||||
|
// Resolve the designation row ID(s) from employee_workorder_details matching |
||||
|
// by ID or text designation name for the given vendor |
||||
|
String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " |
||||
|
+ |
||||
|
"WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0"; |
||||
|
List<Map<String, Object>> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId, |
||||
|
designationIdOrName, designationIdOrName); |
||||
|
if (rows == null || rows.isEmpty()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (Map<String, Object> row : rows) { |
||||
|
String desId = String.valueOf(row.get("id")); |
||||
|
String desName = (String) row.get("designation"); |
||||
|
String tenderStr = (String) row.get("tender_manpower_designation_wise"); |
||||
|
|
||||
|
// Count active employees for this vendor and designation |
||||
|
String countSql = "SELECT COUNT(*) FROM employee_details e " + |
||||
|
"WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " |
||||
|
+ |
||||
|
" AND (e.designation = ? OR e.designation = ?) " + |
||||
|
" AND e.isactive = 1 AND e.isdelete = 0"; |
||||
|
|
||||
|
Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId, |
||||
|
desId, desName); |
||||
|
int available = activeCount != null ? activeCount : 0; |
||||
|
|
||||
|
int tender = 0; |
||||
|
if (tenderStr != null) { |
||||
|
try { |
||||
|
tender = Integer.parseInt(tenderStr.trim()); |
||||
|
} catch (NumberFormatException ignored) { |
||||
|
} |
||||
|
} |
||||
|
int vacant = tender - available; |
||||
|
|
||||
|
String updateSql = "UPDATE employee_workorder_details SET available_manpower = ?, vacant_count = ? WHERE id = ?"; |
||||
|
jdbcManpowerTemplate.update(updateSql, String.valueOf(available), String.valueOf(vacant), desId); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error recalculating designation manpower for designation: " + designationIdOrName |
||||
|
+ ", vendor: " + vendorId + ": " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean deleteEmployee(String empCode) { |
||||
|
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ? AND isdelete = 0"; |
||||
|
try { |
||||
|
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode); |
||||
|
if (list == null || list.isEmpty()) { |
||||
|
return false; |
||||
|
} |
||||
|
Map<String, Object> emp = list.get(0); |
||||
|
String designation = String.valueOf(emp.get("designation")); |
||||
|
String vendorId = String.valueOf(emp.get("vendor_id")); |
||||
|
|
||||
|
String updateSql = "UPDATE employee_details SET isactive = 0, isdelete = 1 WHERE emp_code = ?"; |
||||
|
int rows = jdbcManpowerTemplate.update(updateSql, empCode); |
||||
|
if (rows > 0) { |
||||
|
recalculateDesignationManpower(designation, vendorId); |
||||
|
return true; |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error deleting employee: " + e.getMessage()); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public boolean deleteVendor(String vendorId) { |
||||
|
try { |
||||
|
String selectVendorSql = "SELECT id, vendor_name FROM vendor_details WHERE id = ?"; |
||||
|
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList(selectVendorSql, vendorId); |
||||
|
if (vendorList == null || vendorList.isEmpty()) { |
||||
|
return false; |
||||
|
} |
||||
|
String vendorName = (String) vendorList.get(0).get("vendor_name"); |
||||
|
|
||||
|
// 1. Soft-delete work orders in employee_workorder_details |
||||
|
String updateWorkOrdersSql = "UPDATE employee_workorder_details SET isactive = 0, isdelete = 1 WHERE vid = ?"; |
||||
|
jdbcManpowerTemplate.update(updateWorkOrdersSql, vendorId); |
||||
|
|
||||
|
// 2. Soft-delete employees under this vendor in employee_details |
||||
|
String updateEmployeesSql = "UPDATE employee_details SET isactive = 0, isdelete = 1 WHERE vendor_id = ? OR vendor_id = ?"; |
||||
|
jdbcManpowerTemplate.update(updateEmployeesSql, vendorId, vendorName); |
||||
|
|
||||
|
// 3. Soft-delete the vendor in vendor_details |
||||
|
String updateVendorSql = "UPDATE vendor_details SET isactive = 0, isdelete = 1 WHERE id = ?"; |
||||
|
int rows = jdbcManpowerTemplate.update(updateVendorSql, vendorId); |
||||
|
|
||||
|
return rows > 0; |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error deleting vendor: " + e.getMessage()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getEmployeesByVendorAll(String vendorId) { |
||||
|
String vendorName = null; |
||||
|
try { |
||||
|
List<Map<String, Object>> vendorList = jdbcManpowerTemplate.queryForList( |
||||
|
"SELECT vendor_name FROM vendor_details WHERE id = ?", |
||||
|
vendorId); |
||||
|
if (vendorList != null && !vendorList.isEmpty()) { |
||||
|
vendorName = (String) vendorList.get(0).get("vendor_name"); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching vendor name by ID: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
String sql = "SELECT " + |
||||
|
" e.id, " + |
||||
|
" e.emp_code, " + |
||||
|
" e.name, " + |
||||
|
" e.email, " + |
||||
|
" e.mobile, " + |
||||
|
" e.vendor_id, " + |
||||
|
" e.isactive, " + |
||||
|
" e.isdelete, " + |
||||
|
" e.joined_date, " + |
||||
|
" e.cdate, " + |
||||
|
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + |
||||
|
" COALESCE(ut.usertype, e.user_type) AS user_type, " + |
||||
|
" COALESCE(des.designation, e.designation) AS designation, " + |
||||
|
" COALESCE(loc.location_name, e.location) AS location, " + |
||||
|
" COALESCE(des.File_no, '-') AS work_order " + |
||||
|
"FROM employee_details e " + |
||||
|
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " + |
||||
|
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + |
||||
|
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) " |
||||
|
+ |
||||
|
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) " |
||||
|
+ |
||||
|
"WHERE e.vendor_id = ? OR e.vendor_id = ? " + |
||||
|
"ORDER BY e.name"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql, vendorId, vendorName != null ? vendorName : ""); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching all employees by vendor ID " + vendorId + ": " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public boolean restoreEmployee(String empCode) { |
||||
|
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ?"; |
||||
|
try { |
||||
|
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode); |
||||
|
if (list == null || list.isEmpty()) { |
||||
|
return false; |
||||
|
} |
||||
|
Map<String, Object> emp = list.get(0); |
||||
|
String designation = String.valueOf(emp.get("designation")); |
||||
|
String vendorId = String.valueOf(emp.get("vendor_id")); |
||||
|
|
||||
|
String updateSql = "UPDATE employee_details SET isactive = 1, isdelete = 0 WHERE emp_code = ?"; |
||||
|
int rows = jdbcManpowerTemplate.update(updateSql, empCode); |
||||
|
if (rows > 0) { |
||||
|
recalculateDesignationManpower(designation, vendorId); |
||||
|
return true; |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error restoring employee: " + e.getMessage()); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public List<Map<String, Object>> getAllEmployeesWithDeleted() { |
||||
|
String sql = "SELECT " + |
||||
|
" e.id, " + |
||||
|
" e.emp_code, " + |
||||
|
" e.name, " + |
||||
|
" e.email, " + |
||||
|
" e.mobile, " + |
||||
|
" e.vendor_id, " + |
||||
|
" e.isactive, " + |
||||
|
" e.isdelete, " + |
||||
|
" e.joined_date, " + |
||||
|
" e.cdate, " + |
||||
|
" COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + |
||||
|
" COALESCE(ut.usertype, e.user_type) AS user_type, " + |
||||
|
" COALESCE(des.designation, e.designation) AS designation, " + |
||||
|
" COALESCE(loc.location_name, e.location) AS location, " + |
||||
|
" COALESCE(des.File_no, '-') AS work_order " + |
||||
|
"FROM employee_details e " + |
||||
|
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " + |
||||
|
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + |
||||
|
"LEFT JOIN employee_workorder_details des ON (e.designation = CAST(des.id AS CHAR) OR e.designation = des.designation) " |
||||
|
+ |
||||
|
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) " |
||||
|
+ |
||||
|
"ORDER BY e.name"; |
||||
|
try { |
||||
|
return jdbcManpowerTemplate.queryForList(sql); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("Error fetching all employees with deleted: " + e.getMessage()); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
spring.application.name=gcc-manpower |
||||
|
server.port=8084 |
||||
|
#server.address=0.0.0.0 |
||||
|
|
||||
|
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration |
||||
|
|
||||
|
# =============================== |
||||
|
# MySQL Database Configuration |
||||
|
# =============================== |
||||
|
# Local DB server |
||||
|
spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower |
||||
|
spring.datasource.username=root |
||||
|
spring.datasource.password=root |
||||
|
#spring.datasource.password=1916008_Saec |
||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver |
||||
|
|
||||
|
# spring.datasource.url=jdbc:oracle:thin:@10.1.0.14:1521:chncorp |
||||
|
# spring.datasource.username=fileadjust |
||||
|
# spring.datasource.password=fileadjust |
||||
|
# spring.datasource.driver-class-name=oracle.jdbc.OracleDriver |
||||
|
|
||||
|
# =============================== |
||||
|
# JPA / Hibernate Config |
||||
|
# =============================== |
||||
|
spring.jpa.hibernate.ddl-auto=update |
||||
|
spring.jpa.show-sql=true |
||||
|
# spring.jpa.properties.hibernate.format_sql=true |
||||
|
spring.jpa.open-in-view=true |
||||
|
logging.level.org.hibernate.SQL=DEBUG |
||||
|
# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE |
||||
|
logging.level.org.hibernate.type.descriptor.sql=TRACE |
||||
|
|
||||
|
# Production context path |
||||
|
server.servlet.context-path=/ |
||||
|
|
||||
|
# Disable Thymeleaf caching for hot-swapping templates |
||||
|
spring.thymeleaf.cache=false |
||||
@ -0,0 +1,44 @@ |
|||||
|
spring.application.name=gcc-manpower |
||||
|
server.port=8084 |
||||
|
#server.address=0.0.0.0 |
||||
|
|
||||
|
|
||||
|
assets.base-url=/gcc-manpower/ |
||||
|
fileBaseUrl=https://gccservices.in/gcc_manpower |
||||
|
|
||||
|
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration |
||||
|
|
||||
|
# =============================== |
||||
|
# MySQL Database Configuration |
||||
|
# =============================== |
||||
|
# Local DB server |
||||
|
# spring.datasource.url=jdbc:mysql://localhost:3306/gcc_manpower |
||||
|
# spring.datasource.username=root |
||||
|
# spring.datasource.password=root |
||||
|
|
||||
|
spring.datasource.url=jdbc:mysql://gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306/gcc_manpower |
||||
|
spring.datasource.username=root |
||||
|
spring.datasource.password=gcc-facial-password |
||||
|
|
||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
# =============================== |
||||
|
# JPA / Hibernate Config |
||||
|
# =============================== |
||||
|
spring.jpa.hibernate.ddl-auto=update |
||||
|
spring.jpa.show-sql=true |
||||
|
# spring.jpa.properties.hibernate.format_sql=true |
||||
|
spring.jpa.open-in-view=true |
||||
|
logging.level.org.hibernate.SQL=DEBUG |
||||
|
# logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE |
||||
|
logging.level.org.hibernate.type.descriptor.sql=TRACE |
||||
|
|
||||
|
# Production context path |
||||
|
server.servlet.context-path=/gcc_manpower |
||||
|
|
||||
|
# Disable Thymeleaf caching for hot-swapping templates |
||||
|
spring.thymeleaf.cache=false |
||||
@ -0,0 +1,5 @@ |
|||||
|
spring.profiles.active=local |
||||
|
#spring.profiles.active=prod |
||||
|
spring.application.name=gcc-manpower |
||||
|
server.servlet.session.timeout=10m |
||||
|
|
||||
11794
src/main/resources/static/assets/bootstrap-5.3.2/css/bootstrap.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2212
src/main/resources/static/assets/bootstrap-5.3.2/js/bootstrap.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,193 @@ |
|||||
|
body { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
min-height: 100vh; /* ensures gradient fills screen */ |
||||
|
font-family: Arial, sans-serif; |
||||
|
background: linear-gradient(45deg, #f6f7fb, #4ca3b3); |
||||
|
background-repeat: no-repeat; |
||||
|
} |
||||
|
|
||||
|
.form-title { |
||||
|
text-align: center; |
||||
|
margin-bottom: 20px; |
||||
|
font-size: 1.8rem; |
||||
|
color: #003366; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.header-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.back-button { |
||||
|
background-color: white; |
||||
|
border: 2px solid black !important; |
||||
|
border-radius: 4px; |
||||
|
padding: 10px 20px; |
||||
|
border-radius: 5px; |
||||
|
font-size: 16px; |
||||
|
position: relative; /* override absolute */ |
||||
|
color: #005f73; |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.3s, transform 0.2s; |
||||
|
} |
||||
|
|
||||
|
.back-button:hover { |
||||
|
background: #4ca3b3; |
||||
|
transform: scale(1.05); |
||||
|
color: #000000; |
||||
|
} |
||||
|
|
||||
|
.back-to-home-btn { |
||||
|
background-color: white; |
||||
|
border: 2px solid black !important; |
||||
|
border-radius: 4px; |
||||
|
padding: 10px 20px; |
||||
|
border-radius: 5px; |
||||
|
font-size: 16px; |
||||
|
position: relative; /* override absolute */ |
||||
|
color: #005f73; |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.3s, transform 0.2s; |
||||
|
} |
||||
|
|
||||
|
.back-to-home-btn:hover { |
||||
|
background: #4ca3b3; |
||||
|
transform: scale(1.05); |
||||
|
color: #000000; |
||||
|
} |
||||
|
|
||||
|
.required { |
||||
|
color: red; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
padding: 20px; |
||||
|
max-width: 1200px; |
||||
|
margin: 0 auto; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
color: #0B3954; |
||||
|
margin-bottom: 30px; |
||||
|
} |
||||
|
|
||||
|
.form-container { |
||||
|
background: white; |
||||
|
padding: 30px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.form-row { |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr; |
||||
|
gap: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.form-group { |
||||
|
margin-bottom: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.form-group label { |
||||
|
margin-bottom: 5px; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.form-control { |
||||
|
padding: 8px; |
||||
|
border: 1px solid #ddd; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.file-input { |
||||
|
display: flex; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.file-input button { |
||||
|
background: #f0f0f0; |
||||
|
border: none; |
||||
|
padding: 8px 15px; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.radio-group { |
||||
|
display: flex; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
|
||||
|
.radio-option { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 5px; |
||||
|
} |
||||
|
|
||||
|
.submit-btn { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
|
||||
|
.submit-btn input[type="submit"]:disabled { |
||||
|
background-color: #cccccc; /* grey color */ |
||||
|
color: #666666; |
||||
|
cursor: not-allowed; |
||||
|
} |
||||
|
|
||||
|
.submit-btn input[type="submit"] { |
||||
|
background-color: #005f73; |
||||
|
color: #fff; |
||||
|
font-size: 1rem; |
||||
|
border: none; |
||||
|
padding: 10px 20px; |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: background 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.submit-btn input[type="submit"]:hover { |
||||
|
background: #005f05; |
||||
|
} |
||||
|
/* Responsive Design for Tablets */ |
||||
|
@media ( max-width : 768px) { |
||||
|
.form-row { |
||||
|
grid-template-columns: 1fr; |
||||
|
} |
||||
|
.form-container { |
||||
|
padding: 20px; |
||||
|
} |
||||
|
.form-title { |
||||
|
font-size: 1.5rem; |
||||
|
} |
||||
|
.form-control { |
||||
|
font-size: 1rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Responsive Design for Mobile */ |
||||
|
@media ( max-width : 480px) { |
||||
|
.form-row { |
||||
|
grid-template-columns: 1fr; |
||||
|
} |
||||
|
.submit-btn input[type="submit"] { |
||||
|
width: 100%; |
||||
|
font-size: 1.2rem; |
||||
|
padding: 12px; |
||||
|
} |
||||
|
.form-container { |
||||
|
padding: 15px; |
||||
|
} |
||||
|
.form-title { |
||||
|
font-size: 1.2rem; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,114 @@ |
|||||
|
#custom-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
#custom-search { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; /* pushes it to the right */ |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
#custom-length { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; /* Add spacing between dropdown and label */ |
||||
|
white-space: nowrap; /* Prevent wrapping */ |
||||
|
} |
||||
|
|
||||
|
.dataTables_length label { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 0; |
||||
|
gap: 5px; /* Adjust spacing between text and dropdown */ |
||||
|
} |
||||
|
|
||||
|
.dataTables_length select { |
||||
|
display: inline-block; |
||||
|
width: auto; /* Ensure dropdown adjusts to content */ |
||||
|
} |
||||
|
|
||||
|
#custom-pagination { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; /* Align pagination to the right */ |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.dataTables_filter { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; /* space between label and input */ |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.dataTables_filter label { |
||||
|
margin: 0; |
||||
|
font-weight: 500; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
color: black; |
||||
|
} |
||||
|
|
||||
|
.dataTables_filter input { |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 5px; |
||||
|
padding: 5px 8px; |
||||
|
margin-left: 5px; /* small gap after label */ |
||||
|
outline: none; |
||||
|
width: 180px; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
/* Apply border-radius to the table */ |
||||
|
.det-table { |
||||
|
width: 100%; /* Table only grows as needed */ |
||||
|
border-collapse: collapse; |
||||
|
table-layout: auto; /* Browser decides column width based on content */ |
||||
|
border-spacing: 0; |
||||
|
border: 2px solid #f0f0f0; |
||||
|
margin-top: 20px; |
||||
|
font-family: Arial, sans-serif; |
||||
|
font-size: 14px; |
||||
|
border-radius: 12px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.det-table th, .det-table td { |
||||
|
border: 1px solid #e0e0e0; |
||||
|
padding: 6px 10px; /* Small padding keeps things compact */ |
||||
|
white-space: nowrap; /* Prevents text wrapping → keeps columns narrow */ |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.det-table td.no-word-wrap { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
.det-table th { |
||||
|
background-color: #005f73; |
||||
|
color: white; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.det-table tr:nth-child(even) { |
||||
|
background-color: #f9f9f9; |
||||
|
} |
||||
|
|
||||
|
.det-table tr:hover { |
||||
|
background-color: #f1f1f1; |
||||
|
} |
||||
|
|
||||
|
.det-table th, .det-table td { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 767.98px) { |
||||
|
.btn-group { |
||||
|
width: 100%; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
.btn-group .btn { |
||||
|
width: 48%; /* make buttons fill container nicely */ |
||||
|
margin: 1px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
.header { |
||||
|
background-color: #005f73; |
||||
|
padding: 10px 20px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
/* justify-content: space-between; */ |
||||
|
position: relative; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.header .logo-left img { |
||||
|
height: 60px; |
||||
|
width: 60px; |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
|
||||
|
.header .title { |
||||
|
text-align: center; |
||||
|
/* flex-grow: 1; */ |
||||
|
position: absolute; |
||||
|
transform: translateX(-50%); |
||||
|
left: 50% |
||||
|
} |
||||
|
|
||||
|
.header .title h2 { |
||||
|
margin: 0; |
||||
|
font-size: 1.5rem; |
||||
|
} |
||||
|
|
||||
|
.header .logos-right img { |
||||
|
height: 60px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.header .logo-left { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.header .logo-text { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.header .logo-text h2 { |
||||
|
margin: 0; |
||||
|
font-size: 0.8rem; |
||||
|
color: white; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.header .logo-text p { |
||||
|
margin: 0; |
||||
|
font-size: 0.5rem; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
@media ( max-width : 480px) { |
||||
|
.header { |
||||
|
flex-direction: column; |
||||
|
align-items: flex-start; |
||||
|
position: relative; |
||||
|
} |
||||
|
.header .logo-left img { |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
.header .logos-right img { |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
.header .title { |
||||
|
position: static; /* Reset absolute positioning */ |
||||
|
transform: none; /* Remove the translate effect */ |
||||
|
margin-top: 10px; |
||||
|
text-align: center; /* Ensure the text stays centered */ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,826 @@ |
|||||
|
.button-row { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
gap: 40px; |
||||
|
flex-wrap: wrap; |
||||
|
/* allows wrapping */ |
||||
|
margin-top: 20px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Center heading & align right-side search neatly */ |
||||
|
.row.align-items-center.gx-3.justify-content-between { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.form-title { |
||||
|
margin: 0; |
||||
|
font-weight: 600; |
||||
|
text-align: center; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
/* Ensure dropdown + search align nicely */ |
||||
|
.col-md-6.d-flex.justify-content-md-end { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* Center content properly in larger screens */ |
||||
|
.form-container { |
||||
|
max-width: 1200px; |
||||
|
margin: 0 auto; |
||||
|
/* centers horizontally */ |
||||
|
} |
||||
|
|
||||
|
.form-control { |
||||
|
width: auto !important; |
||||
|
flex: 0 0 auto; |
||||
|
} |
||||
|
|
||||
|
.custom-button { |
||||
|
padding: 15px 30px; |
||||
|
position: relative; |
||||
|
/* Important: makes absolute badge work */ |
||||
|
background: white; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 10px; |
||||
|
font-size: 1rem; |
||||
|
color: #005f73; |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.3s, transform 0.2s; |
||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
||||
|
flex: 1 1 calc(33.333% - 40px); |
||||
|
/* 3 per row, minus gap space */ |
||||
|
max-width: 300px; |
||||
|
/* optional: limit button width */ |
||||
|
min-width: 200px; |
||||
|
/* optional: keep from shrinking too small */ |
||||
|
} |
||||
|
|
||||
|
.custom-button:hover { |
||||
|
background: #4ca3b3; |
||||
|
transform: scale(1.05); |
||||
|
color: #000000; |
||||
|
} |
||||
|
|
||||
|
.custom-button:active { |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
#vendor-filter, |
||||
|
#designation-filter { |
||||
|
min-width: 200px; |
||||
|
border-radius: 8px; |
||||
|
} |
||||
|
|
||||
|
#search-input { |
||||
|
width: 250px; |
||||
|
max-width: 100%; |
||||
|
padding: 8px 12px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
#search-input:focus { |
||||
|
outline: none; |
||||
|
border-color: #4ca3b3; |
||||
|
box-shadow: 0 3px 8px rgba(76, 163, 179, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* === Login Modal Overlay === */ |
||||
|
.login-modal-overlay { |
||||
|
display: none; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
z-index: 1050; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
/* === Modal Box === */ |
||||
|
.login-modal-box { |
||||
|
background: #fff; |
||||
|
width: 400px; |
||||
|
max-width: 90%; |
||||
|
border-radius: 8px; |
||||
|
padding: 25px 30px; |
||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); |
||||
|
position: relative; |
||||
|
animation: fadeIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
/* === Close Button === */ |
||||
|
.close-btn { |
||||
|
position: absolute; |
||||
|
top: 10px; |
||||
|
right: 15px; |
||||
|
font-size: 24px; |
||||
|
color: #666; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #000; |
||||
|
} |
||||
|
|
||||
|
/* === Title === */ |
||||
|
.login-title { |
||||
|
text-align: center; |
||||
|
margin-bottom: 20px; |
||||
|
font-size: 22px; |
||||
|
color: #333; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.login-btn-send { |
||||
|
background-color: #005f73; |
||||
|
color: #fff; |
||||
|
padding: 8px 16px; |
||||
|
border: none; |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
font-weight: 600; |
||||
|
transition: background 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.login-btn-send:hover { |
||||
|
background-color: #4ca3b3; |
||||
|
} |
||||
|
|
||||
|
/* === Form === */ |
||||
|
.login-form .form-label { |
||||
|
display: block; |
||||
|
font-size: 15px; |
||||
|
color: #444; |
||||
|
margin-bottom: 6px; |
||||
|
} |
||||
|
|
||||
|
.req { |
||||
|
color: red; |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
width: 100%; |
||||
|
padding: 10px 12px; |
||||
|
font-size: 15px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.form-input:focus { |
||||
|
border-color: #00bcd4; |
||||
|
box-shadow: 0 0 4px rgba(0, 188, 212, 0.4); |
||||
|
} |
||||
|
|
||||
|
/* === Button Row === */ |
||||
|
.btn-row { |
||||
|
margin-top: 20px; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
/* === Animation states === */ |
||||
|
.login-modal-overlay.show { |
||||
|
animation: fadeIn 0.3s ease forwards; |
||||
|
} |
||||
|
|
||||
|
.login-modal-overlay.hide { |
||||
|
animation: fadeOut 0.3s ease forwards; |
||||
|
} |
||||
|
|
||||
|
.url-num-badge { |
||||
|
position: absolute; |
||||
|
top: -12px; |
||||
|
left: -12px; |
||||
|
background-color: #005f73; |
||||
|
/* or any color */ |
||||
|
color: white; |
||||
|
font-size: 14px; |
||||
|
width: 28px; |
||||
|
height: 28px; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-weight: bold; |
||||
|
z-index: 10; |
||||
|
} |
||||
|
|
||||
|
/* === Fade Animations === */ |
||||
|
@keyframes fadeIn { |
||||
|
from { |
||||
|
transform: scale(0.95); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
to { |
||||
|
transform: scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeOut { |
||||
|
from { |
||||
|
transform: scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
to { |
||||
|
transform: scale(0.95); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Fix heading centering in large screens */ |
||||
|
@media (min-width: 992px) { |
||||
|
.row.align-items-center.gy-3.justify-content-center.text-center { |
||||
|
justify-content: space-between !important; |
||||
|
} |
||||
|
|
||||
|
.row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:first-child { |
||||
|
text-align: center !important; |
||||
|
flex: 1 1 100%; |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
.row.align-items-center.gy-3.justify-content-center.text-center .col-md-6:last-child { |
||||
|
flex: 1 1 auto; |
||||
|
max-width: fit-content; |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Tablet screens - shrink more */ |
||||
|
@media (max-width : 768px) { |
||||
|
.form-title { |
||||
|
font-size: 1.2rem !important; |
||||
|
} |
||||
|
|
||||
|
.button-row { |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
gap: 15px; |
||||
|
/* tighter spacing for small screens */ |
||||
|
} |
||||
|
|
||||
|
.custom-button { |
||||
|
flex: none; |
||||
|
width: 80%; |
||||
|
/* nicely centered buttons */ |
||||
|
max-width: 320px; |
||||
|
min-width: unset; |
||||
|
/* remove constraint */ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Responsive tweak for small screens */ |
||||
|
@media (max-width: 576px) { |
||||
|
.form-title { |
||||
|
font-size: 1.1rem !important; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
#search-input { |
||||
|
width: 100%; |
||||
|
max-width: 280px; |
||||
|
} |
||||
|
|
||||
|
#vendor-filter, |
||||
|
#designation-filter { |
||||
|
width: 100% !important; |
||||
|
max-width: 280px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* */ |
||||
|
|
||||
|
|
||||
|
.vendor-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); |
||||
|
gap: 20px; |
||||
|
margin-top: 20px; |
||||
|
margin-bottom: 40px; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.vendor-card { |
||||
|
background: #ffffff; |
||||
|
border: 1px solid #eef2f6; |
||||
|
border-radius: 12px; |
||||
|
padding: 22px; |
||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02); |
||||
|
transition: all 0.3s ease; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: space-between; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.vendor-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 4px; |
||||
|
height: 100%; |
||||
|
background-color: #0f5c78; |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
|
||||
|
.vendor-card:hover { |
||||
|
transform: translateY(-5px); |
||||
|
box-shadow: 0 8px 25px rgba(15, 92, 120, 0.08); |
||||
|
border-color: #d4e7ee; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: start; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-title { |
||||
|
font-size: 1.15rem; |
||||
|
font-weight: 800; |
||||
|
color: #2c3e50; |
||||
|
margin: 0; |
||||
|
text-transform: capitalize; |
||||
|
line-height: 1.3; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-desc { |
||||
|
font-size: 0.9rem; |
||||
|
color: #7f8c8d; |
||||
|
line-height: 1.4; |
||||
|
margin-bottom: 20px; |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-footer { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding-top: 15px; |
||||
|
border-top: 1px dashed #eef2f6; |
||||
|
} |
||||
|
|
||||
|
.vendor-badge { |
||||
|
font-size: 0.75rem; |
||||
|
font-weight: 700; |
||||
|
text-transform: uppercase; |
||||
|
padding: 4px 10px; |
||||
|
border-radius: 12px; |
||||
|
background-color: #eef7fa; |
||||
|
color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
/* Employee Count Interactive Badge */ |
||||
|
.emp-count-badge { |
||||
|
background: linear-gradient(135deg, #0f5c78, #1e82a6); |
||||
|
color: #ffffff; |
||||
|
font-weight: 800; |
||||
|
font-size: 1.05rem; |
||||
|
padding: 8px 18px; |
||||
|
border-radius: 30px; |
||||
|
cursor: pointer; |
||||
|
border: none; |
||||
|
box-shadow: 0 4px 10px rgba(30, 130, 166, 0.25); |
||||
|
transition: all 0.2s ease; |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.emp-count-badge:hover { |
||||
|
transform: scale(1.08); |
||||
|
box-shadow: 0 6px 15px rgba(30, 130, 166, 0.4); |
||||
|
background: linear-gradient(135deg, #1e82a6, #249cc8); |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
.emp-count-badge:active { |
||||
|
transform: scale(0.96); |
||||
|
} |
||||
|
|
||||
|
/* Modal styling overrides */ |
||||
|
.modal-custom-header { |
||||
|
background-color: #0f5c78; |
||||
|
color: white; |
||||
|
border-top-left-radius: 14px; |
||||
|
border-top-right-radius: 14px; |
||||
|
padding: 20px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.modal-custom-title { |
||||
|
font-weight: 800; |
||||
|
font-size: 1.35rem; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box { |
||||
|
position: relative; |
||||
|
margin-bottom: 20px; |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box input { |
||||
|
padding: 10px 15px 10px 40px; |
||||
|
border: 1px solid #dcdfe3; |
||||
|
border-radius: 8px; |
||||
|
width: 100%; |
||||
|
font-size: 0.95rem; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box input:focus { |
||||
|
border-color: #0f5c78; |
||||
|
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15); |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box i { |
||||
|
position: absolute; |
||||
|
left: 15px; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
color: #7f8c8d; |
||||
|
} |
||||
|
|
||||
|
.modal-table { |
||||
|
font-size: 0.9rem; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.modal-table th { |
||||
|
background-color: #eef7fa !important; |
||||
|
color: #0f5c78 !important; |
||||
|
font-weight: 700; |
||||
|
text-transform: uppercase; |
||||
|
font-size: 0.8rem; |
||||
|
letter-spacing: 0.5px; |
||||
|
border-bottom: 2px solid #0f5c78 !important; |
||||
|
} |
||||
|
|
||||
|
.modal-table td { |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.modal-table-total-row { |
||||
|
font-weight: 800; |
||||
|
border-top: 2px solid #0f5c78 !important; |
||||
|
} |
||||
|
|
||||
|
.modal-table-total-row td { |
||||
|
background-color: #eef7fa !important; |
||||
|
color: #2c3e50 !important; |
||||
|
} |
||||
|
|
||||
|
.modal-body { |
||||
|
padding: 25px; |
||||
|
} |
||||
|
|
||||
|
.export-btn { |
||||
|
background-color: #27ae60; |
||||
|
color: white; |
||||
|
border: none; |
||||
|
padding: 8px 16px; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 700; |
||||
|
border-radius: 6px; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.export-btn:hover { |
||||
|
background-color: #219653; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
/* Summary Cards Section */ |
||||
|
.summary-dashboard { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); |
||||
|
gap: 20px; |
||||
|
margin: 25px 0 35px 0; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.summary-card { |
||||
|
background: #ffffff; |
||||
|
border: 1px solid #eef2f6; |
||||
|
border-radius: 12px; |
||||
|
padding: 20px; |
||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.01); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 15px; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.summary-card:hover { |
||||
|
transform: translateY(-3px); |
||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.05); |
||||
|
border-color: #d4e7ee; |
||||
|
} |
||||
|
|
||||
|
.summary-card.interactive { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.summary-card.interactive:hover { |
||||
|
border-color: #27ae60; |
||||
|
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.12); |
||||
|
} |
||||
|
|
||||
|
.summary-icon-container { |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
border-radius: 10px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 1.5rem; |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
.summary-details { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.summary-title { |
||||
|
font-size: 0.8rem; |
||||
|
font-weight: 700; |
||||
|
color: #7f8c8d; |
||||
|
margin: 0 0 4px 0; |
||||
|
text-transform: uppercase; |
||||
|
letter-spacing: 0.5px; |
||||
|
} |
||||
|
|
||||
|
.summary-value { |
||||
|
font-size: 1.6rem; |
||||
|
font-weight: 800; |
||||
|
color: #2c3e50; |
||||
|
margin: 0; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
/* Custom Colors for Icons */ |
||||
|
.bg-summary-vendors { |
||||
|
background: linear-gradient(135deg, #7f8c8d, #95a5a6); |
||||
|
} |
||||
|
|
||||
|
.bg-summary-tender { |
||||
|
background: linear-gradient(135deg, #0f5c78, #1e82a6); |
||||
|
} |
||||
|
|
||||
|
.bg-summary-existing { |
||||
|
background: linear-gradient(135deg, #27ae60, #2ecc71); |
||||
|
} |
||||
|
|
||||
|
.bg-summary-vacant { |
||||
|
background: linear-gradient(135deg, #d35400, #e67e22); |
||||
|
} |
||||
|
|
||||
|
/* Vendor Card Designation List Styles */ |
||||
|
.vendor-desig-section { |
||||
|
margin-top: 15px; |
||||
|
margin-bottom: 18px; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-title { |
||||
|
font-size: 0.75rem; |
||||
|
font-weight: 800; |
||||
|
color: #0f5c78; |
||||
|
text-transform: uppercase; |
||||
|
letter-spacing: 0.8px; |
||||
|
margin-bottom: 8px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-container { |
||||
|
background-color: #f8fafc; |
||||
|
border: 1px solid #f1f5f9; |
||||
|
border-radius: 8px; |
||||
|
padding: 10px 14px; |
||||
|
max-height: 140px; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 6px 0; |
||||
|
border-bottom: 1px solid #f1f5f9; |
||||
|
font-size: 0.8rem; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-row:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-row.clickable { |
||||
|
cursor: pointer; |
||||
|
transition: background-color 0.2s ease, padding-left 0.2s ease, padding-right 0.2s ease; |
||||
|
border-radius: 4px; |
||||
|
padding: 6px 8px; |
||||
|
margin: 1px -8px; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-row.clickable:hover { |
||||
|
background-color: #eef7fa; |
||||
|
padding-left: 12px; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-name { |
||||
|
color: #334155; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-counts { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-vacant-badge { |
||||
|
background-color: #fee2e2; |
||||
|
color: #ef4444; |
||||
|
font-weight: 700; |
||||
|
font-size: 0.72rem; |
||||
|
padding: 2px 6px; |
||||
|
border-radius: 4px; |
||||
|
border: 1px solid #fca5a5; |
||||
|
} |
||||
|
|
||||
|
.vendor-desig-tender-label { |
||||
|
color: #64748b; |
||||
|
font-size: 0.7rem; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* Premium Date Filter Styling */ |
||||
|
.date-filter-container { |
||||
|
display: inline-flex; |
||||
|
align-items: flex-end; |
||||
|
justify-content: center; |
||||
|
gap: 15px; |
||||
|
background: #ffffff; |
||||
|
padding: 15px 25px; |
||||
|
border-radius: 12px; |
||||
|
border: 1px solid #e2e8f0; |
||||
|
box-shadow: 0 4px 12px rgba(15, 92, 120, 0.05); |
||||
|
margin: 15px auto 20px auto; |
||||
|
max-width: 100%; |
||||
|
flex-wrap: wrap; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.date-filter-container:hover { |
||||
|
box-shadow: 0 6px 18px rgba(15, 92, 120, 0.08); |
||||
|
border-color: #cbd5e1; |
||||
|
} |
||||
|
|
||||
|
.date-input-group { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: flex-start; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.date-input-group label { |
||||
|
font-size: 0.75rem; |
||||
|
font-weight: 800; |
||||
|
color: #0f5c78; |
||||
|
text-transform: uppercase; |
||||
|
letter-spacing: 0.5px; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.date-picker-input { |
||||
|
padding: 8px 36px 8px 12px; |
||||
|
border: 1.5px solid #cbd5e1; |
||||
|
border-radius: 8px; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 600; |
||||
|
color: #334155; |
||||
|
background-color: #f8fafc; |
||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%237f8c8d'%3E%3Cpath d='M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192z'/%3E%3C/svg%3E"); |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: right 12px center; |
||||
|
background-size: 16px 16px; |
||||
|
transition: all 0.3s ease; |
||||
|
outline: none; |
||||
|
height: 38px; |
||||
|
width: 175px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.date-picker-input:focus, |
||||
|
.date-picker-input:hover { |
||||
|
border-color: #0f5c78; |
||||
|
background-color: #ffffff; |
||||
|
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.12); |
||||
|
} |
||||
|
|
||||
|
.date-filter-separator { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 38px; |
||||
|
color: #94a3b8; |
||||
|
font-size: 0.9rem; |
||||
|
} |
||||
|
|
||||
|
.date-filter-btn { |
||||
|
height: 38px; |
||||
|
padding: 0 20px; |
||||
|
background: linear-gradient(135deg, #0f5c78, #1e82a6); |
||||
|
color: #ffffff; |
||||
|
font-weight: 700; |
||||
|
font-size: 0.9rem; |
||||
|
border: none; |
||||
|
border-radius: 8px; |
||||
|
cursor: pointer; |
||||
|
box-shadow: 0 4px 8px rgba(30, 130, 166, 0.2); |
||||
|
transition: all 0.2s ease; |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
} |
||||
|
|
||||
|
.date-filter-btn:hover { |
||||
|
background: linear-gradient(135deg, #1e82a6, #249cc8); |
||||
|
box-shadow: 0 5px 12px rgba(30, 130, 166, 0.3); |
||||
|
transform: translateY(-1px); |
||||
|
} |
||||
|
|
||||
|
.date-filter-btn:active { |
||||
|
transform: translateY(1px); |
||||
|
} |
||||
|
|
||||
|
/* Responsive styling for date filter */ |
||||
|
@media (max-width: 576px) { |
||||
|
.date-filter-container { |
||||
|
flex-direction: column; |
||||
|
align-items: stretch; |
||||
|
width: 100%; |
||||
|
padding: 15px; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.date-input-group { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.date-picker-input { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.date-filter-separator { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.date-filter-btn { |
||||
|
justify-content: center; |
||||
|
margin-top: 5px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 100; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2") format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 200; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-200.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 300; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-300.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 400; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-regular.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 500; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-500.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 600; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-600.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 700; |
||||
|
src: url("../poppins-23.0/fonts/devanagari/poppins-v23-devanagari-700.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
|
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 100; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-100.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 200; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-200.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 300; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-300.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 400; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-regular.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 500; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-500.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 600; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-600.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 700; |
||||
|
src: url("../poppins-23.0/fonts/latin-ext/poppins-v23-latin-ext-700.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 100; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-100.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 200; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-200.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 300; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-300.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 400; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-regular.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 500; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-500.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 600; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-600.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
@font-face { |
||||
|
font-family: "Poppins"; |
||||
|
font-style: normal; |
||||
|
font-weight: 700; |
||||
|
src: url("../poppins-23.0/fonts/latin/poppins-v23-latin-700.woff2") |
||||
|
format("woff2"); |
||||
|
} |
||||
|
|
||||
|
* { |
||||
|
margin: 0; |
||||
|
border: 0; |
||||
|
padding: 0; |
||||
|
box-sizing: border-box; |
||||
|
font-family: "Poppins", sans-serif; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
background: linear-gradient(45deg, #f6f7fb, #4ca3b3); |
||||
|
background-repeat: no-repeat; |
||||
|
min-height: 100vh; |
||||
|
min-width: 100vw; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 100%; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
div.container { |
||||
|
background: white; |
||||
|
min-width: 320px; |
||||
|
min-height: 33vh; |
||||
|
padding: 2rem; |
||||
|
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.2); |
||||
|
border-radius: 8px; |
||||
|
} |
||||
|
|
||||
|
div h2 { |
||||
|
font-weight: 600; |
||||
|
margin-bottom: 2rem; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.container h3, |
||||
|
.container h4 { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
div h2::before { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
height: 4px; |
||||
|
width: 25px; |
||||
|
bottom: 3px; |
||||
|
left: 0; |
||||
|
border-radius: 8px; |
||||
|
background: #005f73; |
||||
|
} |
||||
|
|
||||
|
form { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.input-field { |
||||
|
position: relative; |
||||
|
margin-bottom: 1.5rem; /* instead of targeting only first-child */ |
||||
|
} |
||||
|
|
||||
|
.input-field .underline::before { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
height: 1px; |
||||
|
width: 100%; |
||||
|
bottom: -5px; |
||||
|
left: 0; |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.input-field .underline::after { |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
height: 1px; |
||||
|
width: 100%; |
||||
|
bottom: -5px; |
||||
|
left: 0; |
||||
|
background: linear-gradient(45deg, #8e2de2, #4a00e0); |
||||
|
transform: scaleX(0); |
||||
|
transition: all 0.3s ease-in-out; |
||||
|
transform-origin: left; |
||||
|
} |
||||
|
|
||||
|
.input-field input:focus ~ .underline::after { |
||||
|
transform: scaleX(1); |
||||
|
} |
||||
|
|
||||
|
.input-field input { |
||||
|
outline: none; |
||||
|
font-size: 0.9rem; |
||||
|
color: rgba(0, 0, 0, 0.7); |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.input-field input::placeholder { |
||||
|
color: rgba(0, 0, 0, 0.5); |
||||
|
} |
||||
|
|
||||
|
form input[type="submit"] { |
||||
|
margin-top: 2rem; |
||||
|
padding: 0.4rem; |
||||
|
width: 100%; |
||||
|
background: #005f73; |
||||
|
cursor: pointer; |
||||
|
color: white; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 300; |
||||
|
border-radius: 4px; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
form input[type="submit"]:hover { |
||||
|
letter-spacing: 0.5px; |
||||
|
background: #005f05; |
||||
|
} |
||||
|
|
||||
|
.small-logo { |
||||
|
width: 60px; |
||||
|
height: 60px; |
||||
|
display: block; |
||||
|
margin: 0 auto; /* Centers the image */ |
||||
|
} |
||||
@ -0,0 +1,532 @@ |
|||||
|
:root { |
||||
|
--dt-row-selected: 13, 110, 253; |
||||
|
--dt-row-selected-text: 255, 255, 255; |
||||
|
--dt-row-selected-link: 9, 10, 11; |
||||
|
--dt-row-stripe: 0, 0, 0; |
||||
|
--dt-row-hover: 0, 0, 0; |
||||
|
--dt-column-ordering: 0, 0, 0; |
||||
|
--dt-html-background: white; |
||||
|
} |
||||
|
|
||||
|
:root.dark { |
||||
|
--dt-html-background: rgb(33, 37, 41); |
||||
|
} |
||||
|
|
||||
|
table.dataTable td.dt-control { |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
table.dataTable td.dt-control:before { |
||||
|
display: inline-block; |
||||
|
color: rgba(0, 0, 0, 0.5); |
||||
|
content: "►"; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tr.dt-hasChild td.dt-control:before { |
||||
|
content: "▼"; |
||||
|
} |
||||
|
|
||||
|
html.dark table.dataTable td.dt-control:before { |
||||
|
color: rgba(255, 255, 255, 0.5); |
||||
|
} |
||||
|
|
||||
|
html.dark table.dataTable tr.dt-hasChild td.dt-control:before { |
||||
|
color: rgba(255, 255, 255, 0.5); |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting, |
||||
|
table.dataTable thead > tr > th.sorting_asc, |
||||
|
table.dataTable thead > tr > th.sorting_desc, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled, |
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled, |
||||
|
table.dataTable thead > tr > td.sorting, |
||||
|
table.dataTable thead > tr > td.sorting_asc, |
||||
|
table.dataTable thead > tr > td.sorting_desc, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled { |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
padding-right: 26px; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting:before, |
||||
|
table.dataTable thead > tr > th.sorting:after, |
||||
|
table.dataTable thead > tr > th.sorting_asc:before, |
||||
|
table.dataTable thead > tr > th.sorting_asc:after, |
||||
|
table.dataTable thead > tr > th.sorting_desc:before, |
||||
|
table.dataTable thead > tr > th.sorting_desc:after, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled:before, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled:after, |
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled:before, |
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled:after, |
||||
|
table.dataTable thead > tr > td.sorting:before, |
||||
|
table.dataTable thead > tr > td.sorting:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc:before, |
||||
|
table.dataTable thead > tr > td.sorting_asc:after, |
||||
|
table.dataTable thead > tr > td.sorting_desc:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled:before, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled:after, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled:after { |
||||
|
position: absolute; |
||||
|
display: block; |
||||
|
opacity: 0.125; |
||||
|
right: 10px; |
||||
|
line-height: 9px; |
||||
|
font-size: 0.8em; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting:before, |
||||
|
table.dataTable thead > tr > th.sorting_asc:before, |
||||
|
table.dataTable thead > tr > th.sorting_desc:before, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled:before, |
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled:before, |
||||
|
table.dataTable thead > tr > td.sorting:before, |
||||
|
table.dataTable thead > tr > td.sorting_asc:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc:before, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled:before { |
||||
|
bottom: 50%; |
||||
|
content: "▲"; |
||||
|
content: "▲"/""; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting:after, |
||||
|
table.dataTable thead > tr > th.sorting_asc:after, |
||||
|
table.dataTable thead > tr > th.sorting_desc:after, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled:after, |
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled:after, |
||||
|
table.dataTable thead > tr > td.sorting:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc:after, |
||||
|
table.dataTable thead > tr > td.sorting_desc:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled:after, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled:after { |
||||
|
top: 50%; |
||||
|
content: "▼"; |
||||
|
content: "▼"/""; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting_asc:before, |
||||
|
table.dataTable thead > tr > th.sorting_desc:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc:after { |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th.sorting_desc_disabled:after, |
||||
|
table.dataTable thead > tr > th.sorting_asc_disabled:before, |
||||
|
table.dataTable thead > tr > td.sorting_desc_disabled:after, |
||||
|
table.dataTable thead > tr > td.sorting_asc_disabled:before { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead > tr > th:active, |
||||
|
table.dataTable thead > tr > td:active { |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, |
||||
|
div.dataTables_scrollBody > table.dataTable > thead > tr > th:after, |
||||
|
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before, |
||||
|
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
width: 200px; |
||||
|
margin-left: -100px; |
||||
|
margin-top: -26px; |
||||
|
text-align: center; |
||||
|
padding: 2px; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child { |
||||
|
position: relative; |
||||
|
width: 80px; |
||||
|
height: 15px; |
||||
|
margin: 1em auto; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child > div { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
width: 13px; |
||||
|
height: 13px; |
||||
|
border-radius: 50%; |
||||
|
background: rgb(13, 110, 253); |
||||
|
background: rgb(var(--dt-row-selected)); |
||||
|
animation-timing-function: cubic-bezier(0, 1, 1, 0); |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child > div:nth-child(1) { |
||||
|
left: 8px; |
||||
|
animation: datatables-loader-1 0.6s infinite; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child > div:nth-child(2) { |
||||
|
left: 8px; |
||||
|
animation: datatables-loader-2 0.6s infinite; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child > div:nth-child(3) { |
||||
|
left: 32px; |
||||
|
animation: datatables-loader-2 0.6s infinite; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_processing > div:last-child > div:nth-child(4) { |
||||
|
left: 56px; |
||||
|
animation: datatables-loader-3 0.6s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes datatables-loader-1 { |
||||
|
0% { |
||||
|
transform: scale(0); |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: scale(1); |
||||
|
} |
||||
|
} |
||||
|
@keyframes datatables-loader-3 { |
||||
|
0% { |
||||
|
transform: scale(1); |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: scale(0); |
||||
|
} |
||||
|
} |
||||
|
@keyframes datatables-loader-2 { |
||||
|
0% { |
||||
|
transform: translate(0, 0); |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
transform: translate(24px, 0); |
||||
|
} |
||||
|
} |
||||
|
table.dataTable.nowrap th, |
||||
|
table.dataTable.nowrap td { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th.dt-left, |
||||
|
table.dataTable td.dt-left { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th.dt-center, |
||||
|
table.dataTable td.dt-center, |
||||
|
table.dataTable td.dataTables_empty { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th.dt-right, |
||||
|
table.dataTable td.dt-right { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th.dt-justify, |
||||
|
table.dataTable td.dt-justify { |
||||
|
text-align: justify; |
||||
|
} |
||||
|
|
||||
|
table.dataTable th.dt-nowrap, |
||||
|
table.dataTable td.dt-nowrap { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th, |
||||
|
table.dataTable thead td, |
||||
|
table.dataTable tfoot th, |
||||
|
table.dataTable tfoot td { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th.dt-head-left, |
||||
|
table.dataTable thead td.dt-head-left, |
||||
|
table.dataTable tfoot th.dt-head-left, |
||||
|
table.dataTable tfoot td.dt-head-left { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th.dt-head-center, |
||||
|
table.dataTable thead td.dt-head-center, |
||||
|
table.dataTable tfoot th.dt-head-center, |
||||
|
table.dataTable tfoot td.dt-head-center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th.dt-head-right, |
||||
|
table.dataTable thead td.dt-head-right, |
||||
|
table.dataTable tfoot th.dt-head-right, |
||||
|
table.dataTable tfoot td.dt-head-right { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th.dt-head-justify, |
||||
|
table.dataTable thead td.dt-head-justify, |
||||
|
table.dataTable tfoot th.dt-head-justify, |
||||
|
table.dataTable tfoot td.dt-head-justify { |
||||
|
text-align: justify; |
||||
|
} |
||||
|
|
||||
|
table.dataTable thead th.dt-head-nowrap, |
||||
|
table.dataTable thead td.dt-head-nowrap, |
||||
|
table.dataTable tfoot th.dt-head-nowrap, |
||||
|
table.dataTable tfoot td.dt-head-nowrap { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tbody th.dt-body-left, |
||||
|
table.dataTable tbody td.dt-body-left { |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tbody th.dt-body-center, |
||||
|
table.dataTable tbody td.dt-body-center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tbody th.dt-body-right, |
||||
|
table.dataTable tbody td.dt-body-right { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tbody th.dt-body-justify, |
||||
|
table.dataTable tbody td.dt-body-justify { |
||||
|
text-align: justify; |
||||
|
} |
||||
|
|
||||
|
table.dataTable tbody th.dt-body-nowrap, |
||||
|
table.dataTable tbody td.dt-body-nowrap { |
||||
|
white-space: nowrap; |
||||
|
} /*! Bootstrap 5 integration for DataTables |
||||
|
* |
||||
|
* ©2020 SpryMedia Ltd, all rights reserved. |
||||
|
* License: MIT datatables.net/license/mit |
||||
|
*/ |
||||
|
table.dataTable { |
||||
|
clear: both; |
||||
|
margin-top: 6px !important; |
||||
|
margin-bottom: 6px !important; |
||||
|
max-width: none !important; |
||||
|
border-collapse: separate !important; |
||||
|
border-spacing: 0; |
||||
|
} |
||||
|
|
||||
|
table.dataTable td, |
||||
|
table.dataTable th { |
||||
|
-webkit-box-sizing: content-box; |
||||
|
box-sizing: content-box; |
||||
|
} |
||||
|
|
||||
|
table.dataTable td.dataTables_empty, |
||||
|
table.dataTable th.dataTables_empty { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
table.dataTable.nowrap th, |
||||
|
table.dataTable.nowrap td { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-striped > tbody > tr:nth-of-type(2n + 1) > * { |
||||
|
box-shadow: none; |
||||
|
} |
||||
|
|
||||
|
table.dataTable > tbody > tr { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
|
||||
|
table.dataTable > tbody > tr.selected > * { |
||||
|
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253); |
||||
|
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected)); |
||||
|
color: rgb(255, 255, 255); |
||||
|
color: rgb(var(--dt-row-selected-text)); |
||||
|
} |
||||
|
|
||||
|
table.dataTable > tbody > tr.selected a { |
||||
|
color: rgb(9, 10, 11); |
||||
|
color: rgb(var(--dt-row-selected-link)); |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-striped > tbody > tr.odd > * { |
||||
|
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05); |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-striped > tbody > tr.odd.selected > * { |
||||
|
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95); |
||||
|
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95); |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-hover > tbody > tr:hover > * { |
||||
|
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075); |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-hover > tbody > tr.selected:hover > * { |
||||
|
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975); |
||||
|
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975); |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_length label { |
||||
|
font-weight: normal; |
||||
|
text-align: left; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_length select { |
||||
|
width: auto; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_filter { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_filter label { |
||||
|
font-weight: normal; |
||||
|
white-space: nowrap; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_filter input { |
||||
|
margin-left: 0.5em; |
||||
|
display: inline-block; |
||||
|
width: auto; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_info { |
||||
|
padding-top: 0.85em; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_paginate { |
||||
|
margin: 0; |
||||
|
white-space: nowrap; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dataTables_paginate ul.pagination { |
||||
|
margin: 2px 0; |
||||
|
white-space: nowrap; |
||||
|
justify-content: flex-end; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_wrapper div.dt-row { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollHead table.dataTable { |
||||
|
margin-bottom: 0 !important; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollBody > table { |
||||
|
border-top: none; |
||||
|
margin-top: 0 !important; |
||||
|
margin-bottom: 0 !important; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollBody > table > thead .sorting:before, |
||||
|
div.dataTables_scrollBody > table > thead .sorting_asc:before, |
||||
|
div.dataTables_scrollBody > table > thead .sorting_desc:before, |
||||
|
div.dataTables_scrollBody > table > thead .sorting:after, |
||||
|
div.dataTables_scrollBody > table > thead .sorting_asc:after, |
||||
|
div.dataTables_scrollBody > table > thead .sorting_desc:after { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollBody > table > tbody tr:first-child th, |
||||
|
div.dataTables_scrollBody > table > tbody tr:first-child td { |
||||
|
border-top: none; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollFoot > .dataTables_scrollFootInner { |
||||
|
box-sizing: content-box; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table { |
||||
|
margin-top: 0 !important; |
||||
|
border-top: none; |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: 767px) { |
||||
|
div.dataTables_wrapper div.dataTables_length, |
||||
|
div.dataTables_wrapper div.dataTables_filter, |
||||
|
div.dataTables_wrapper div.dataTables_info, |
||||
|
div.dataTables_wrapper div.dataTables_paginate { |
||||
|
text-align: center; |
||||
|
} |
||||
|
div.dataTables_wrapper div.dataTables_paginate ul.pagination { |
||||
|
justify-content: center !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) { |
||||
|
padding-right: 20px; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable { |
||||
|
border-right-width: 0; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable thead tr:first-child th, |
||||
|
table.table-bordered.dataTable thead tr:first-child td { |
||||
|
border-top-width: 1px; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable th, |
||||
|
table.table-bordered.dataTable td { |
||||
|
border-left-width: 0; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable th:first-child, |
||||
|
table.table-bordered.dataTable th:first-child, |
||||
|
table.table-bordered.dataTable td:first-child, |
||||
|
table.table-bordered.dataTable td:first-child { |
||||
|
border-left-width: 1px; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable th:last-child, |
||||
|
table.table-bordered.dataTable th:last-child, |
||||
|
table.table-bordered.dataTable td:last-child, |
||||
|
table.table-bordered.dataTable td:last-child { |
||||
|
border-right-width: 1px; |
||||
|
} |
||||
|
|
||||
|
table.table-bordered.dataTable th, |
||||
|
table.table-bordered.dataTable td { |
||||
|
border-bottom-width: 1px; |
||||
|
} |
||||
|
|
||||
|
div.dataTables_scrollHead table.table-bordered { |
||||
|
border-bottom-width: 0; |
||||
|
} |
||||
|
|
||||
|
div.table-responsive > div.dataTables_wrapper > div.row { |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
div.table-responsive |
||||
|
> div.dataTables_wrapper |
||||
|
> div.row |
||||
|
> div[class^="col-"]:first-child { |
||||
|
padding-left: 0; |
||||
|
} |
||||
|
|
||||
|
div.table-responsive |
||||
|
> div.dataTables_wrapper |
||||
|
> div.row |
||||
|
> div[class^="col-"]:last-child { |
||||
|
padding-right: 0; |
||||
|
} |
||||
|
|
||||
|
:root[data-bs-theme="dark"] { |
||||
|
--dt-row-hover: 255, 255, 255; |
||||
|
--dt-row-stripe: 255, 255, 255; |
||||
|
--dt-column-ordering: 255, 255, 255; |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
/*! DataTables Bootstrap 5 integration |
||||
|
* 2020 SpryMedia Ltd - datatables.net/license |
||||
|
*/ |
||||
|
! function(t) { |
||||
|
var n, r; |
||||
|
"function" == typeof define && define.amd ? define(["jquery", "datatables.net"], function(e) { |
||||
|
return t(e, window, document) |
||||
|
}) : "object" == typeof exports ? (n = require("jquery"), r = function(e, a) { |
||||
|
a.fn.dataTable || require("datatables.net")(e, a) |
||||
|
}, "undefined" == typeof window ? module.exports = function(e, a) { |
||||
|
return e = e || window, a = a || n(e), r(e, a), t(a, 0, e.document) |
||||
|
} : (r(window, n), module.exports = t(n, window, window.document))) : t(jQuery, window, document) |
||||
|
}(function(x, e, r, o) { |
||||
|
"use strict"; |
||||
|
var i = x.fn.dataTable; |
||||
|
return x.extend(!0, i.defaults, { |
||||
|
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row dt-row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", |
||||
|
renderer: "bootstrap" |
||||
|
}), x.extend(i.ext.classes, { |
||||
|
sWrapper: "dataTables_wrapper dt-bootstrap5", |
||||
|
sFilterInput: "form-control form-control-sm", |
||||
|
sLengthSelect: "form-select form-select-sm", |
||||
|
sProcessing: "dataTables_processing card", |
||||
|
sPageButton: "paginate_button page-item" |
||||
|
}), i.ext.renderer.pageButton.bootstrap = function(d, e, s, a, l, c) { |
||||
|
function u(e, a) { |
||||
|
for (var t, n, r = function(e) { |
||||
|
e.preventDefault(), x(e.currentTarget).hasClass("disabled") || b.page() == e.data.action || b.page(e.data.action).draw("page") |
||||
|
}, o = 0, i = a.length; o < i; o++) |
||||
|
if (t = a[o], Array.isArray(t)) u(e, t); |
||||
|
else { |
||||
|
switch (f = p = "", t) { |
||||
|
case "ellipsis": |
||||
|
p = "…", f = "disabled"; |
||||
|
break; |
||||
|
case "first": |
||||
|
p = g.sFirst, f = t + (0 < l ? "" : " disabled"); |
||||
|
break; |
||||
|
case "previous": |
||||
|
p = g.sPrevious, f = t + (0 < l ? "" : " disabled"); |
||||
|
break; |
||||
|
case "next": |
||||
|
p = g.sNext, f = t + (l < c - 1 ? "" : " disabled"); |
||||
|
break; |
||||
|
case "last": |
||||
|
p = g.sLast, f = t + (l < c - 1 ? "" : " disabled"); |
||||
|
break; |
||||
|
default: |
||||
|
p = t + 1, f = l === t ? "active" : "" |
||||
|
} |
||||
|
p && (n = -1 !== f.indexOf("disabled"), n = x("<li>", { |
||||
|
class: m.sPageButton + " " + f, |
||||
|
id: 0 === s && "string" == typeof t ? d.sTableId + "_" + t : null |
||||
|
}).append(x("<a>", { |
||||
|
href: n ? null : "#", |
||||
|
"aria-controls": d.sTableId, |
||||
|
"aria-disabled": n ? "true" : null, |
||||
|
"aria-label": w[t], |
||||
|
role: "link", |
||||
|
"aria-current": "active" === f ? "page" : null, |
||||
|
"data-dt-idx": t, |
||||
|
tabindex: n ? -1 : d.iTabIndex, |
||||
|
class: "page-link" |
||||
|
}).html(p)).appendTo(e), d.oApi._fnBindAction(n, { |
||||
|
action: t |
||||
|
}, r)) |
||||
|
} |
||||
|
} |
||||
|
var p, f, t, b = new i.Api(d), |
||||
|
m = d.oClasses, |
||||
|
g = d.oLanguage.oPaginate, |
||||
|
w = d.oLanguage.oAria.paginate || {}, |
||||
|
e = x(e); |
||||
|
try { |
||||
|
t = e.find(r.activeElement).data("dt-idx") |
||||
|
} catch (e) { } |
||||
|
var n = e.children("ul.pagination"); |
||||
|
n.length ? n.empty() : n = e.html("<ul/>").children("ul").addClass("pagination"), u(n, a), t !== o && e.find("[data-dt-idx=" + t + "]").trigger("focus") |
||||
|
}, i |
||||
|
}); |
||||
2990
src/main/resources/static/assets/datatables-1.13.6/js/jquery.dataTables.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,248 @@ |
|||||
|
@keyframes dtb-spinner { |
||||
|
100% { |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
@-o-keyframes dtb-spinner { |
||||
|
100% { |
||||
|
-o-transform: rotate(360deg); |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
@-ms-keyframes dtb-spinner { |
||||
|
100% { |
||||
|
-ms-transform: rotate(360deg); |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
@-webkit-keyframes dtb-spinner { |
||||
|
100% { |
||||
|
-webkit-transform: rotate(360deg); |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
@-moz-keyframes dtb-spinner { |
||||
|
100% { |
||||
|
-moz-transform: rotate(360deg); |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
div.dataTables_wrapper { |
||||
|
position: relative; |
||||
|
} |
||||
|
div.dt-buttons { |
||||
|
position: initial; |
||||
|
} |
||||
|
div.dt-buttons .dt-button { |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
div.dt-button-info { |
||||
|
position: fixed; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
width: 400px; |
||||
|
margin-top: -100px; |
||||
|
margin-left: -200px; |
||||
|
background-color: white; |
||||
|
border-radius: 0.75em; |
||||
|
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8); |
||||
|
text-align: center; |
||||
|
z-index: 2003; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
div.dt-button-info h2 { |
||||
|
padding: 2rem 2rem 1rem 2rem; |
||||
|
margin: 0; |
||||
|
font-weight: normal; |
||||
|
} |
||||
|
div.dt-button-info > div { |
||||
|
padding: 1em 2em 2em 2em; |
||||
|
} |
||||
|
div.dtb-popover-close { |
||||
|
position: absolute; |
||||
|
top: 6px; |
||||
|
right: 6px; |
||||
|
width: 22px; |
||||
|
height: 22px; |
||||
|
text-align: center; |
||||
|
border-radius: 3px; |
||||
|
cursor: pointer; |
||||
|
z-index: 2003; |
||||
|
} |
||||
|
button.dtb-hide-drop { |
||||
|
display: none !important; |
||||
|
} |
||||
|
div.dt-button-collection-title { |
||||
|
text-align: center; |
||||
|
padding: 0.3em 0 0.5em; |
||||
|
margin-left: 0.5em; |
||||
|
margin-right: 0.5em; |
||||
|
font-size: 0.9em; |
||||
|
} |
||||
|
div.dt-button-collection-title:empty { |
||||
|
display: none; |
||||
|
} |
||||
|
span.dt-button-spacer { |
||||
|
display: inline-block; |
||||
|
margin: 0.5em; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
span.dt-button-spacer.bar { |
||||
|
border-left: 1px solid rgba(0, 0, 0, 0.3); |
||||
|
vertical-align: middle; |
||||
|
padding-left: 0.5em; |
||||
|
} |
||||
|
span.dt-button-spacer.bar:empty { |
||||
|
height: 1em; |
||||
|
width: 1px; |
||||
|
padding-left: 0; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active { |
||||
|
padding-right: 3em; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active:after { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
margin-top: -10px; |
||||
|
right: 1em; |
||||
|
display: inline-block; |
||||
|
content: "✓"; |
||||
|
color: inherit; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active.dt-button-split { |
||||
|
padding-right: 0; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active.dt-button-split:after { |
||||
|
display: none; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active.dt-button-split > *:first-child { |
||||
|
padding-right: 3em; |
||||
|
} |
||||
|
div.dt-button-collection |
||||
|
.dt-button-active.dt-button-split |
||||
|
> *:first-child:after { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
margin-top: -10px; |
||||
|
right: 1em; |
||||
|
display: inline-block; |
||||
|
content: "✓"; |
||||
|
color: inherit; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active-a a { |
||||
|
padding-right: 3em; |
||||
|
} |
||||
|
div.dt-button-collection .dt-button-active-a a:after { |
||||
|
position: absolute; |
||||
|
right: 1em; |
||||
|
display: inline-block; |
||||
|
content: "✓"; |
||||
|
color: inherit; |
||||
|
} |
||||
|
div.dt-button-collection span.dt-button-spacer { |
||||
|
width: 100%; |
||||
|
font-size: 0.9em; |
||||
|
text-align: center; |
||||
|
margin: 0.5em 0; |
||||
|
} |
||||
|
div.dt-button-collection span.dt-button-spacer:empty { |
||||
|
height: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
div.dt-button-collection span.dt-button-spacer.bar { |
||||
|
border-left: none; |
||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1); |
||||
|
padding-left: 0; |
||||
|
} |
||||
|
html.dark div.dt-button-info { |
||||
|
background-color: var(--dt-html-background); |
||||
|
border: 1px solid rgba(255, 255, 255, 0.15); |
||||
|
} |
||||
|
div.dt-buttons div.btn-group { |
||||
|
position: initial; |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu { |
||||
|
margin-top: 4px; |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu .dt-button { |
||||
|
position: relative; |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu div.dt-button-split { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
flex-wrap: wrap; |
||||
|
justify-content: flex-start; |
||||
|
align-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu div.dt-button-split a:first-child { |
||||
|
min-width: auto; |
||||
|
flex: 1 0 50px; |
||||
|
padding-right: 0; |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child { |
||||
|
min-width: 33px; |
||||
|
flex: 0; |
||||
|
background: transparent; |
||||
|
border: none; |
||||
|
line-height: 1rem; |
||||
|
color: var(--bs-dropdown-link-color); |
||||
|
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:hover { |
||||
|
color: var(--bs-dropdown-link-hover-color); |
||||
|
background-color: var(--bs-dropdown-link-hover-bg); |
||||
|
} |
||||
|
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:after { |
||||
|
position: relative; |
||||
|
left: -3px; |
||||
|
} |
||||
|
div.dt-buttons span.dt-button-spacer.empty { |
||||
|
margin: 1px; |
||||
|
} |
||||
|
div.dt-buttons span.dt-button-spacer.bar:empty { |
||||
|
height: inherit; |
||||
|
} |
||||
|
div.dt-buttons .btn.processing { |
||||
|
color: rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
div.dt-buttons .btn.processing:after { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
width: 16px; |
||||
|
height: 16px; |
||||
|
margin: -8px 0 0 -8px; |
||||
|
box-sizing: border-box; |
||||
|
display: block; |
||||
|
content: " "; |
||||
|
border: 2px solid rgb(40, 40, 40); |
||||
|
border-radius: 50%; |
||||
|
border-left-color: transparent; |
||||
|
border-right-color: transparent; |
||||
|
animation: dtb-spinner 1500ms infinite linear; |
||||
|
-o-animation: dtb-spinner 1500ms infinite linear; |
||||
|
-ms-animation: dtb-spinner 1500ms infinite linear; |
||||
|
-webkit-animation: dtb-spinner 1500ms infinite linear; |
||||
|
-moz-animation: dtb-spinner 1500ms infinite linear; |
||||
|
} |
||||
|
div.dt-button-background { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
z-index: 999; |
||||
|
} |
||||
|
@media screen and (max-width: 767px) { |
||||
|
div.dt-buttons { |
||||
|
float: none; |
||||
|
width: 100%; |
||||
|
text-align: center; |
||||
|
margin-bottom: 0.5em; |
||||
|
} |
||||
|
div.dt-buttons a.btn { |
||||
|
float: none; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
/*! Bootstrap integration for DataTables' Buttons |
||||
|
* © SpryMedia Ltd - datatables.net/license |
||||
|
*/ |
||||
|
! function(o) { |
||||
|
var e, a; |
||||
|
"function" == typeof define && define.amd ? define(["jquery", "datatables.net-bs5", "datatables.net-buttons"], function(t) { |
||||
|
return o(t, window, document) |
||||
|
}) : "object" == typeof exports ? (e = require("jquery"), a = function(t, n) { |
||||
|
n.fn.dataTable || require("datatables.net-bs5")(t, n), n.fn.dataTable.Buttons || require("datatables.net-buttons")(t, n) |
||||
|
}, "undefined" == typeof window ? module.exports = function(t, n) { |
||||
|
return t = t || window, n = n || e(t), a(t, n), o(n, 0, t.document) |
||||
|
} : (a(window, e), module.exports = o(e, window, window.document))) : o(jQuery, window, document) |
||||
|
}(function(o, t, n, e) { |
||||
|
"use strict"; |
||||
|
var a = o.fn.dataTable; |
||||
|
return o.extend(!0, a.Buttons.defaults, { |
||||
|
dom: { |
||||
|
container: { |
||||
|
className: "dt-buttons btn-group flex-wrap" |
||||
|
}, |
||||
|
button: { |
||||
|
className: "btn btn-secondary", |
||||
|
active: "active" |
||||
|
}, |
||||
|
collection: { |
||||
|
action: { |
||||
|
dropHtml: "" |
||||
|
}, |
||||
|
container: { |
||||
|
tag: "div", |
||||
|
className: "dropdown-menu dt-button-collection" |
||||
|
}, |
||||
|
closeButton: !1, |
||||
|
button: { |
||||
|
tag: "a", |
||||
|
className: "dt-button dropdown-item", |
||||
|
active: "dt-button-active", |
||||
|
disabled: "disabled", |
||||
|
spacer: { |
||||
|
className: "dropdown-divider", |
||||
|
tag: "hr" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
split: { |
||||
|
action: { |
||||
|
tag: "a", |
||||
|
className: "btn btn-secondary dt-button-split-drop-button", |
||||
|
closeButton: !1 |
||||
|
}, |
||||
|
dropdown: { |
||||
|
tag: "button", |
||||
|
dropHtml: "", |
||||
|
className: "btn btn-secondary dt-button-split-drop dropdown-toggle dropdown-toggle-split", |
||||
|
closeButton: !1, |
||||
|
align: "split-left", |
||||
|
splitAlignClass: "dt-button-split-left" |
||||
|
}, |
||||
|
wrapper: { |
||||
|
tag: "div", |
||||
|
className: "dt-button-split btn-group", |
||||
|
closeButton: !1 |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
buttonCreated: function(t, n) { |
||||
|
return t.buttons ? o('<div class="btn-group"/>').append(n) : n |
||||
|
} |
||||
|
}), a.ext.buttons.collection.className += " dropdown-toggle", a.ext.buttons.collection.rightAlignClassName = "dropdown-menu-right", a |
||||
|
}); |
||||
538
src/main/resources/static/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,79 @@ |
|||||
|
/*! |
||||
|
* Print button for Buttons and DataTables. |
||||
|
* © SpryMedia Ltd - datatables.net/license |
||||
|
*/ |
||||
|
! function(n) { |
||||
|
var o, r; |
||||
|
"function" == typeof define && define.amd ? define(["jquery", "datatables.net", "datatables.net-buttons"], function(t) { |
||||
|
return n(t, window, document) |
||||
|
}) : "object" == typeof exports ? (o = require("jquery"), r = function(t, e) { |
||||
|
e.fn.dataTable || require("datatables.net")(t, e), e.fn.dataTable.Buttons || require("datatables.net-buttons")(t, e) |
||||
|
}, "undefined" == typeof window ? module.exports = function(t, e) { |
||||
|
return t = t || window, e = e || o(t), r(t, e), n(e, t, t.document) |
||||
|
} : (r(window, o), module.exports = n(o, window, window.document))) : n(jQuery, window, document) |
||||
|
}(function(m, b, t, p) { |
||||
|
"use strict"; |
||||
|
|
||||
|
function h(t) { |
||||
|
return n.href = t, -1 === (t = n.host).indexOf("/") && 0 !== n.pathname.indexOf("/") && (t += "/"), n.protocol + "//" + t + n.pathname + n.search |
||||
|
} |
||||
|
var e = m.fn.dataTable, |
||||
|
n = t.createElement("a"); |
||||
|
return e.ext.buttons.print = { |
||||
|
className: "buttons-print", |
||||
|
text: function(t) { |
||||
|
return t.i18n("buttons.print", "Print") |
||||
|
}, |
||||
|
action: function(t, e, n, o) { |
||||
|
function r(t, e) { |
||||
|
for (var n = "<tr>", o = 0, r = t.length; o < r; o++) { |
||||
|
var i = null === t[o] || t[o] === p ? "" : t[o]; |
||||
|
n += "<" + e + " " + (s[o] ? 'class="' + s[o] + '"' : "") + ">" + i + "</" + e + ">" |
||||
|
} |
||||
|
return n + "</tr>" |
||||
|
} |
||||
|
var i = e.buttons.exportData(m.extend({ |
||||
|
decodeEntities: !1 |
||||
|
}, o.exportOptions)), |
||||
|
a = e.buttons.exportInfo(o), |
||||
|
s = e.columns(o.exportOptions.columns).flatten().map(function(t) { |
||||
|
return e.settings()[0].aoColumns[e.column(t).index()].sClass |
||||
|
}).toArray(), |
||||
|
u = '<table class="' + e.table().node().className + '">'; |
||||
|
o.header && (u += "<thead>" + r(i.header, "th") + "</thead>"), u += "<tbody>"; |
||||
|
for (var d = 0, c = i.body.length; d < c; d++) u += r(i.body[d], "td"); |
||||
|
u += "</tbody>", o.footer && i.footer && (u += "<tfoot>" + r(i.footer, "th") + "</tfoot>"), u += "</table>"; |
||||
|
var l = b.open("", ""); |
||||
|
if (l) { |
||||
|
l.document.close(); |
||||
|
var f = "<title>" + a.title + "</title>"; |
||||
|
m("style, link").each(function() { |
||||
|
f += function(t) { |
||||
|
t = m(t).clone()[0]; |
||||
|
return "link" === t.nodeName.toLowerCase() && (t.href = h(t.href)), t.outerHTML |
||||
|
}(this) |
||||
|
}); |
||||
|
try { |
||||
|
l.document.head.innerHTML = f |
||||
|
} catch (t) { |
||||
|
m(l.document.head).html(f) |
||||
|
} |
||||
|
l.document.body.innerHTML = "<h1>" + a.title + "</h1><div>" + (a.messageTop || "") + "</div>" + u + "<div>" + (a.messageBottom || "") + "</div>", m(l.document.body).addClass("dt-print-view"), m("img", l.document.body).each(function(t, e) { |
||||
|
e.setAttribute("src", h(e.getAttribute("src"))) |
||||
|
}), o.customize && o.customize(l, o, e); |
||||
|
a = function() { |
||||
|
o.autoPrint && (l.print(), l.close()) |
||||
|
}; |
||||
|
navigator.userAgent.match(/Trident\/\d.\d/) ? a() : l.setTimeout(a, 1e3) |
||||
|
} else e.buttons.info(e.i18n("buttons.printErrorTitle", "Unable to open print view"), e.i18n("buttons.printErrorMsg", "Please allow popups in your browser for this site to be able to view the print view."), 5e3) |
||||
|
}, |
||||
|
title: "*", |
||||
|
messageTop: "*", |
||||
|
messageBottom: "*", |
||||
|
exportOptions: {}, |
||||
|
header: !0, |
||||
|
footer: !1, |
||||
|
autoPrint: !0, |
||||
|
customize: null |
||||
|
}, e |
||||
|
}); |
||||
@ -0,0 +1,764 @@ |
|||||
|
/*! Buttons for DataTables 2.4.1 |
||||
|
* © SpryMedia Ltd - datatables.net/license |
||||
|
*/ |
||||
|
! function(e) { |
||||
|
var o, i; |
||||
|
"function" == typeof define && define.amd ? define(["jquery", "datatables.net"], function(t) { |
||||
|
return e(t, window, document) |
||||
|
}) : "object" == typeof exports ? (o = require("jquery"), i = function(t, n) { |
||||
|
n.fn.dataTable || require("datatables.net")(t, n) |
||||
|
}, "undefined" == typeof window ? module.exports = function(t, n) { |
||||
|
return t = t || window, n = n || o(t), i(t, n), e(n, t, t.document) |
||||
|
} : (i(window, o), module.exports = e(o, window, window.document))) : e(jQuery, window, document) |
||||
|
}(function(y, v, x, w) { |
||||
|
"use strict"; |
||||
|
var e = y.fn.dataTable, |
||||
|
o = 0, |
||||
|
C = 0, |
||||
|
_ = e.ext.buttons; |
||||
|
|
||||
|
function A(t, n, e) { |
||||
|
y.fn.animate ? t.stop().fadeIn(n, e) : (t.css("display", "block"), e && e.call(t)) |
||||
|
} |
||||
|
|
||||
|
function k(t, n, e) { |
||||
|
y.fn.animate ? t.stop().fadeOut(n, e) : (t.css("display", "none"), e && e.call(t)) |
||||
|
} |
||||
|
|
||||
|
function B(n, t) { |
||||
|
if (!(this instanceof B)) return function(t) { |
||||
|
return new B(t, n).container() |
||||
|
}; |
||||
|
!0 === (t = void 0 === t ? {} : t) && (t = {}), Array.isArray(t) && (t = { |
||||
|
buttons: t |
||||
|
}), this.c = y.extend(!0, {}, B.defaults, t), t.buttons && (this.c.buttons = t.buttons), this.s = { |
||||
|
dt: new e.Api(n), |
||||
|
buttons: [], |
||||
|
listenKeys: "", |
||||
|
namespace: "dtb" + o++ |
||||
|
}, this.dom = { |
||||
|
container: y("<" + this.c.dom.container.tag + "/>").addClass(this.c.dom.container.className) |
||||
|
}, this._constructor() |
||||
|
} |
||||
|
y.extend(B.prototype, { |
||||
|
action: function(t, n) { |
||||
|
t = this._nodeToButton(t); |
||||
|
return n === w ? t.conf.action : (t.conf.action = n, this) |
||||
|
}, |
||||
|
active: function(t, n) { |
||||
|
var t = this._nodeToButton(t), |
||||
|
e = this.c.dom.button.active, |
||||
|
o = y(t.node); |
||||
|
return t.inCollection && this.c.dom.collection.button && this.c.dom.collection.button.active !== w && (e = this.c.dom.collection.button.active), n === w ? o.hasClass(e) : (o.toggleClass(e, n === w || n), this) |
||||
|
}, |
||||
|
add: function(t, n, e) { |
||||
|
var o = this.s.buttons; |
||||
|
if ("string" == typeof n) { |
||||
|
for (var i = n.split("-"), s = this.s, a = 0, r = i.length - 1; a < r; a++) s = s.buttons[+i[a]]; |
||||
|
o = s.buttons, n = +i[i.length - 1] |
||||
|
} |
||||
|
return this._expandButton(o, t, t !== w ? t.split : w, (t === w || t.split === w || 0 === t.split.length) && s !== w, !1, n), e !== w && !0 !== e || this._draw(), this |
||||
|
}, |
||||
|
collectionRebuild: function(t, n) { |
||||
|
var e = this._nodeToButton(t); |
||||
|
if (n !== w) { |
||||
|
for (var o = e.buttons.length - 1; 0 <= o; o--) this.remove(e.buttons[o].node); |
||||
|
for (e.conf.prefixButtons && n.unshift.apply(n, e.conf.prefixButtons), e.conf.postfixButtons && n.push.apply(n, e.conf.postfixButtons), o = 0; o < n.length; o++) { |
||||
|
var i = n[o]; |
||||
|
this._expandButton(e.buttons, i, i !== w && i.config !== w && i.config.split !== w, !0, i.parentConf !== w && i.parentConf.split !== w, null, i.parentConf) |
||||
|
} |
||||
|
} |
||||
|
this._draw(e.collection, e.buttons) |
||||
|
}, |
||||
|
container: function() { |
||||
|
return this.dom.container |
||||
|
}, |
||||
|
disable: function(t) { |
||||
|
t = this._nodeToButton(t); |
||||
|
return y(t.node).addClass(this.c.dom.button.disabled).prop("disabled", !0), this |
||||
|
}, |
||||
|
destroy: function() { |
||||
|
y("body").off("keyup." + this.s.namespace); |
||||
|
for (var t = this.s.buttons.slice(), n = 0, e = t.length; n < e; n++) this.remove(t[n].node); |
||||
|
this.dom.container.remove(); |
||||
|
var o = this.s.dt.settings()[0]; |
||||
|
for (n = 0, e = o.length; n < e; n++) |
||||
|
if (o.inst === this) { |
||||
|
o.splice(n, 1); |
||||
|
break |
||||
|
} return this |
||||
|
}, |
||||
|
enable: function(t, n) { |
||||
|
return !1 === n ? this.disable(t) : (n = this._nodeToButton(t), y(n.node).removeClass(this.c.dom.button.disabled).prop("disabled", !1), this) |
||||
|
}, |
||||
|
index: function(t, n, e) { |
||||
|
n || (n = "", e = this.s.buttons); |
||||
|
for (var o = 0, i = e.length; o < i; o++) { |
||||
|
var s = e[o].buttons; |
||||
|
if (e[o].node === t) return n + o; |
||||
|
if (s && s.length) { |
||||
|
s = this.index(t, o + "-", s); |
||||
|
if (null !== s) return s |
||||
|
} |
||||
|
} |
||||
|
return null |
||||
|
}, |
||||
|
name: function() { |
||||
|
return this.c.name |
||||
|
}, |
||||
|
node: function(t) { |
||||
|
return t ? (t = this._nodeToButton(t), y(t.node)) : this.dom.container |
||||
|
}, |
||||
|
processing: function(t, n) { |
||||
|
var e = this.s.dt, |
||||
|
o = this._nodeToButton(t); |
||||
|
return n === w ? y(o.node).hasClass("processing") : (y(o.node).toggleClass("processing", n), y(e.table().node()).triggerHandler("buttons-processing.dt", [n, e.button(t), e, y(t), o.conf]), this) |
||||
|
}, |
||||
|
remove: function(t) { |
||||
|
var n = this._nodeToButton(t), |
||||
|
e = this._nodeToHost(t), |
||||
|
o = this.s.dt; |
||||
|
if (n.buttons.length) |
||||
|
for (var i = n.buttons.length - 1; 0 <= i; i--) this.remove(n.buttons[i].node); |
||||
|
n.conf.destroying = !0, n.conf.destroy && n.conf.destroy.call(o.button(t), o, y(t), n.conf), this._removeKey(n.conf), y(n.node).remove(); |
||||
|
o = y.inArray(n, e); |
||||
|
return e.splice(o, 1), this |
||||
|
}, |
||||
|
text: function(t, n) { |
||||
|
function e(t) { |
||||
|
return "function" == typeof t ? t(i, s, o.conf) : t |
||||
|
} |
||||
|
var o = this._nodeToButton(t), |
||||
|
t = o.textNode, |
||||
|
i = this.s.dt, |
||||
|
s = y(o.node); |
||||
|
return n === w ? e(o.conf.text) : (o.conf.text = n, t.html(e(n)), this) |
||||
|
}, |
||||
|
_constructor: function() { |
||||
|
var e = this, |
||||
|
t = this.s.dt, |
||||
|
o = t.settings()[0], |
||||
|
n = this.c.buttons; |
||||
|
o._buttons || (o._buttons = []), o._buttons.push({ |
||||
|
inst: this, |
||||
|
name: this.c.name |
||||
|
}); |
||||
|
for (var i = 0, s = n.length; i < s; i++) this.add(n[i]); |
||||
|
t.on("destroy", function(t, n) { |
||||
|
n === o && e.destroy() |
||||
|
}), y("body").on("keyup." + this.s.namespace, function(t) { |
||||
|
var n; |
||||
|
x.activeElement && x.activeElement !== x.body || (n = String.fromCharCode(t.keyCode).toLowerCase(), -1 !== e.s.listenKeys.toLowerCase().indexOf(n) && e._keypress(n, t)) |
||||
|
}) |
||||
|
}, |
||||
|
_addKey: function(t) { |
||||
|
t.key && (this.s.listenKeys += (y.isPlainObject(t.key) ? t.key : t).key) |
||||
|
}, |
||||
|
_draw: function(t, n) { |
||||
|
t || (t = this.dom.container, n = this.s.buttons), t.children().detach(); |
||||
|
for (var e = 0, o = n.length; e < o; e++) t.append(n[e].inserter), t.append(" "), n[e].buttons && n[e].buttons.length && this._draw(n[e].collection, n[e].buttons) |
||||
|
}, |
||||
|
_expandButton: function(t, n, e, o, i, s, a) { |
||||
|
for (var r, l = this.s.dt, c = this.c.dom.collection, u = Array.isArray(n) ? n : [n], d = 0, f = (u = n === w ? Array.isArray(e) ? e : [e] : u).length; d < f; d++) { |
||||
|
var p = this._resolveExtends(u[d]); |
||||
|
if (p) |
||||
|
if (r = !(!p.config || !p.config.split), Array.isArray(p)) this._expandButton(t, p, h !== w && h.conf !== w ? h.conf.split : w, o, a !== w && a.split !== w, s, a); |
||||
|
else { |
||||
|
var h = this._buildButton(p, o, p.split !== w || p.config !== w && p.config.split !== w, i); |
||||
|
if (h) { |
||||
|
if (s !== w && null !== s ? (t.splice(s, 0, h), s++) : t.push(h), h.conf.buttons && (h.collection = y("<" + c.container.content.tag + "/>"), h.conf._collection = h.collection, y(h.node).append(c.action.dropHtml), this._expandButton(h.buttons, h.conf.buttons, h.conf.split, !r, r, s, h.conf)), h.conf.split) { |
||||
|
h.collection = y("<" + c.container.tag + "/>"), h.conf._collection = h.collection; |
||||
|
for (var b = 0; b < h.conf.split.length; b++) { |
||||
|
var g = h.conf.split[b]; |
||||
|
"object" == typeof g && (g.parent = a, g.collectionLayout === w && (g.collectionLayout = h.conf.collectionLayout), g.dropup === w && (g.dropup = h.conf.dropup), g.fade === w) && (g.fade = h.conf.fade) |
||||
|
} |
||||
|
this._expandButton(h.buttons, h.conf.buttons, h.conf.split, !r, r, s, h.conf) |
||||
|
} |
||||
|
h.conf.parent = a, p.init && p.init.call(l.button(h.node), l, y(h.node), p) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
_buildButton: function(n, t, e, o) { |
||||
|
function i(t) { |
||||
|
return "function" == typeof t ? t(u, l, n) : t |
||||
|
} |
||||
|
var s, a, r, l, c = this.c.dom, |
||||
|
u = this.s.dt, |
||||
|
d = y.extend(!0, {}, c.button); |
||||
|
if (t && e && c.collection.split ? y.extend(!0, d, c.collection.split.action) : o || t ? y.extend(!0, d, c.collection.button) : e && y.extend(!0, d, c.split.button), n.spacer) return c = y("<" + d.spacer.tag + "/>").addClass("dt-button-spacer " + n.style + " " + d.spacer.className).html(i(n.text)), { |
||||
|
conf: n, |
||||
|
node: c, |
||||
|
inserter: c, |
||||
|
buttons: [], |
||||
|
inCollection: t, |
||||
|
isSplit: e, |
||||
|
collection: null, |
||||
|
textNode: c |
||||
|
}; |
||||
|
if (n.available && !n.available(u, n) && !n.hasOwnProperty("html")) return !1; |
||||
|
n.hasOwnProperty("html") ? l = y(n.html) : (a = function(t, n, e, o) { |
||||
|
o.action.call(n.button(e), t, n, e, o), y(n.table().node()).triggerHandler("buttons-action.dt", [n.button(e), n, e, o]) |
||||
|
}, c = n.tag || d.tag, r = n.clickBlurs === w || n.clickBlurs, l = y("<" + c + "/>").addClass(d.className).attr("tabindex", this.s.dt.settings()[0].iTabIndex).attr("aria-controls", this.s.dt.table().node().id).on("click.dtb", function(t) { |
||||
|
t.preventDefault(), !l.hasClass(d.disabled) && n.action && a(t, u, l, n), r && l.trigger("blur") |
||||
|
}).on("keypress.dtb", function(t) { |
||||
|
13 === t.keyCode && (t.preventDefault(), !l.hasClass(d.disabled)) && n.action && a(t, u, l, n) |
||||
|
}), "a" === c.toLowerCase() && l.attr("href", "#"), "button" === c.toLowerCase() && l.attr("type", "button"), s = d.liner.tag ? (c = y("<" + d.liner.tag + "/>").html(i(n.text)).addClass(d.liner.className), "a" === d.liner.tag.toLowerCase() && c.attr("href", "#"), l.append(c), c) : (l.html(i(n.text)), l), !1 === n.enabled && l.addClass(d.disabled), n.className && l.addClass(n.className), n.titleAttr && l.attr("title", i(n.titleAttr)), n.attr && l.attr(n.attr), n.namespace || (n.namespace = ".dt-button-" + C++), n.config !== w && n.config.split && (n.split = n.config.split)); |
||||
|
var f, p, h, b, g, m, c = this.c.dom.buttonContainer, |
||||
|
c = c && c.tag ? y("<" + c.tag + "/>").addClass(c.className).append(l) : l; |
||||
|
return this._addKey(n), this.c.buttonCreated && (c = this.c.buttonCreated(n, c)), e && (p = (f = t ? y.extend(!0, this.c.dom.split, this.c.dom.collection.split) : this.c.dom.split).wrapper, h = y("<" + p.tag + "/>").addClass(p.className).append(l), b = y.extend(n, { |
||||
|
align: f.dropdown.align, |
||||
|
attr: { |
||||
|
"aria-haspopup": "dialog", |
||||
|
"aria-expanded": !1 |
||||
|
}, |
||||
|
className: f.dropdown.className, |
||||
|
closeButton: !1, |
||||
|
splitAlignClass: f.dropdown.splitAlignClass, |
||||
|
text: f.dropdown.text |
||||
|
}), this._addKey(b), g = function(t, n, e, o) { |
||||
|
_.split.action.call(n.button(h), t, n, e, o), y(n.table().node()).triggerHandler("buttons-action.dt", [n.button(e), n, e, o]), e.attr("aria-expanded", !0) |
||||
|
}, m = y('<button class="' + f.dropdown.className + ' dt-button"></button>').html(f.dropdown.dropHtml).on("click.dtb", function(t) { |
||||
|
t.preventDefault(), t.stopPropagation(), m.hasClass(d.disabled) || g(t, u, m, b), r && m.trigger("blur") |
||||
|
}).on("keypress.dtb", function(t) { |
||||
|
13 === t.keyCode && (t.preventDefault(), m.hasClass(d.disabled) || g(t, u, m, b)) |
||||
|
}), 0 === n.split.length && m.addClass("dtb-hide-drop"), h.append(m).attr(b.attr)), { |
||||
|
conf: n, |
||||
|
node: (e ? h : l).get(0), |
||||
|
inserter: e ? h : c, |
||||
|
buttons: [], |
||||
|
inCollection: t, |
||||
|
isSplit: e, |
||||
|
inSplit: o, |
||||
|
collection: null, |
||||
|
textNode: s |
||||
|
} |
||||
|
}, |
||||
|
_nodeToButton: function(t, n) { |
||||
|
for (var e = 0, o = (n = n || this.s.buttons).length; e < o; e++) { |
||||
|
if (n[e].node === t) return n[e]; |
||||
|
if (n[e].buttons.length) { |
||||
|
var i = this._nodeToButton(t, n[e].buttons); |
||||
|
if (i) return i |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
_nodeToHost: function(t, n) { |
||||
|
for (var e = 0, o = (n = n || this.s.buttons).length; e < o; e++) { |
||||
|
if (n[e].node === t) return n; |
||||
|
if (n[e].buttons.length) { |
||||
|
var i = this._nodeToHost(t, n[e].buttons); |
||||
|
if (i) return i |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
_keypress: function(s, a) { |
||||
|
var r; |
||||
|
a._buttonsHandled || (r = function(t) { |
||||
|
for (var n, e, o = 0, i = t.length; o < i; o++) n = t[o].conf, e = t[o].node, !n.key || n.key !== s && (!y.isPlainObject(n.key) || n.key.key !== s || n.key.shiftKey && !a.shiftKey || n.key.altKey && !a.altKey || n.key.ctrlKey && !a.ctrlKey || n.key.metaKey && !a.metaKey) || (a._buttonsHandled = !0, y(e).click()), t[o].buttons.length && r(t[o].buttons) |
||||
|
})(this.s.buttons) |
||||
|
}, |
||||
|
_removeKey: function(t) { |
||||
|
var n; |
||||
|
t.key && (t = (y.isPlainObject(t.key) ? t.key : t).key, n = this.s.listenKeys.split(""), t = y.inArray(t, n), n.splice(t, 1), this.s.listenKeys = n.join("")) |
||||
|
}, |
||||
|
_resolveExtends: function(e) { |
||||
|
function t(t) { |
||||
|
for (var n = 0; !y.isPlainObject(t) && !Array.isArray(t);) { |
||||
|
if (t === w) return; |
||||
|
if ("function" == typeof t) { |
||||
|
if (!(t = t.call(i, s, e))) return !1 |
||||
|
} else if ("string" == typeof t) { |
||||
|
if (!_[t]) return { |
||||
|
html: t |
||||
|
}; |
||||
|
t = _[t] |
||||
|
} |
||||
|
if (30 < ++n) throw "Buttons: Too many iterations" |
||||
|
} |
||||
|
return Array.isArray(t) ? t : y.extend({}, t) |
||||
|
} |
||||
|
var n, o, i = this, |
||||
|
s = this.s.dt; |
||||
|
for (e = t(e); e && e.extend;) { |
||||
|
if (!_[e.extend]) throw "Cannot extend unknown button type: " + e.extend; |
||||
|
var a = t(_[e.extend]); |
||||
|
if (Array.isArray(a)) return a; |
||||
|
if (!a) return !1; |
||||
|
var r = a.className; |
||||
|
e.config !== w && a.config !== w && (e.config = y.extend({}, a.config, e.config)), e = y.extend({}, a, e), r && e.className !== r && (e.className = r + " " + e.className), e.extend = a.extend |
||||
|
} |
||||
|
var l = e.postfixButtons; |
||||
|
if (l) |
||||
|
for (e.buttons || (e.buttons = []), n = 0, o = l.length; n < o; n++) e.buttons.push(l[n]); |
||||
|
var c = e.prefixButtons; |
||||
|
if (c) |
||||
|
for (e.buttons || (e.buttons = []), n = 0, o = c.length; n < o; n++) e.buttons.splice(n, 0, c[n]); |
||||
|
return e |
||||
|
}, |
||||
|
_popover: function(o, t, n, e) { |
||||
|
function i() { |
||||
|
h = !0, k(y(g), b.fade, function() { |
||||
|
y(this).detach() |
||||
|
}), y(f.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()).attr("aria-expanded", "false"), y("div.dt-button-background").off("click.dtb-collection"), B.background(!1, b.backgroundClassName, b.fade, m), y(v).off("resize.resize.dtb-collection"), y("body").off(".dtb-collection"), f.off("buttons-action.b-internal"), f.off("destroy") |
||||
|
} |
||||
|
var s, a, r, l, c, u, d, f = t, |
||||
|
p = this.c, |
||||
|
h = !1, |
||||
|
b = y.extend({ |
||||
|
align: "button-left", |
||||
|
autoClose: !1, |
||||
|
background: !0, |
||||
|
backgroundClassName: "dt-button-background", |
||||
|
closeButton: !0, |
||||
|
containerClassName: p.dom.collection.container.className, |
||||
|
contentClassName: p.dom.collection.container.content.className, |
||||
|
collectionLayout: "", |
||||
|
collectionTitle: "", |
||||
|
dropup: !1, |
||||
|
fade: 400, |
||||
|
popoverTitle: "", |
||||
|
rightAlignClassName: "dt-button-right", |
||||
|
tag: p.dom.collection.container.tag |
||||
|
}, n), |
||||
|
g = b.tag + "." + b.containerClassName.replace(/ /g, "."), |
||||
|
m = t.node(); |
||||
|
!1 === o ? i() : ((p = y(f.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes())).length && (m.closest(g).length && (m = p.eq(0)), i()), n = y(".dt-button", o).length, p = "", 3 === n ? p = "dtb-b3" : 2 === n ? p = "dtb-b2" : 1 === n && (p = "dtb-b1"), s = y("<" + b.tag + "/>").addClass(b.containerClassName).addClass(b.collectionLayout).addClass(b.splitAlignClass).addClass(p).css("display", "none").attr({ |
||||
|
"aria-modal": !0, |
||||
|
role: "dialog" |
||||
|
}), o = y(o).addClass(b.contentClassName).attr("role", "menu").appendTo(s), m.attr("aria-expanded", "true"), m.parents("body")[0] !== x.body && (m = x.body.lastChild), b.popoverTitle ? s.prepend('<div class="dt-button-collection-title">' + b.popoverTitle + "</div>") : b.collectionTitle && s.prepend('<div class="dt-button-collection-title">' + b.collectionTitle + "</div>"), b.closeButton && s.prepend('<div class="dtb-popover-close">×</div>').addClass("dtb-collection-closeable"), A(s.insertAfter(m), b.fade), n = y(t.table().container()), d = s.css("position"), "container" !== b.span && "dt-container" !== b.align || (m = m.parent(), s.css("width", n.width())), "absolute" === d ? (p = y(m[0].offsetParent), t = m.position(), n = m.offset(), a = p.offset(), r = p.position(), l = v.getComputedStyle(p[0]), a.height = p.outerHeight(), a.width = p.width() + parseFloat(l.paddingLeft), a.right = a.left + a.width, a.bottom = a.top + a.height, p = t.top + m.outerHeight(), c = t.left, s.css({ |
||||
|
top: p, |
||||
|
left: c |
||||
|
}), l = v.getComputedStyle(s[0]), (u = s.offset()).height = s.outerHeight(), u.width = s.outerWidth(), u.right = u.left + u.width, u.bottom = u.top + u.height, u.marginTop = parseFloat(l.marginTop), u.marginBottom = parseFloat(l.marginBottom), b.dropup && (p = t.top - u.height - u.marginTop - u.marginBottom), "button-right" !== b.align && !s.hasClass(b.rightAlignClassName) || (c = t.left - u.width + m.outerWidth()), "dt-container" !== b.align && "container" !== b.align || (c = c < t.left ? -t.left : c) + u.width > a.width && (c = a.width - u.width), r.left + c + u.width > y(v).width() && (c = y(v).width() - u.width - r.left), n.left + c < 0 && (c = -n.left), r.top + p + u.height > y(v).height() + y(v).scrollTop() && (p = t.top - u.height - u.marginTop - u.marginBottom), r.top + p < y(v).scrollTop() && (p = t.top + m.outerHeight()), s.css({ |
||||
|
top: p, |
||||
|
left: c |
||||
|
})) : ((d = function() { |
||||
|
var t = y(v).height() / 2, |
||||
|
n = s.height() / 2; |
||||
|
s.css("marginTop", -1 * (n = t < n ? t : n)) |
||||
|
})(), y(v).on("resize.dtb-collection", function() { |
||||
|
d() |
||||
|
})), b.background && B.background(!0, b.backgroundClassName, b.fade, b.backgroundHost || m), y("div.dt-button-background").on("click.dtb-collection", function() { }), b.autoClose && setTimeout(function() { |
||||
|
f.on("buttons-action.b-internal", function(t, n, e, o) { |
||||
|
o[0] !== m[0] && i() |
||||
|
}) |
||||
|
}, 0), y(s).trigger("buttons-popover.dt"), f.on("destroy", i), setTimeout(function() { |
||||
|
h = !1, y("body").on("click.dtb-collection", function(t) { |
||||
|
var n, e; |
||||
|
!h && (n = y.fn.addBack ? "addBack" : "andSelf", e = y(t.target).parent()[0], !y(t.target).parents()[n]().filter(o).length && !y(e).hasClass("dt-buttons") || y(t.target).hasClass("dt-button-background")) && i() |
||||
|
}).on("keyup.dtb-collection", function(t) { |
||||
|
27 === t.keyCode && i() |
||||
|
}).on("keydown.dtb-collection", function(t) { |
||||
|
var n = y("a, button", o), |
||||
|
e = x.activeElement; |
||||
|
9 === t.keyCode && (-1 === n.index(e) ? (n.first().focus(), t.preventDefault()) : t.shiftKey ? e === n[0] && (n.last().focus(), t.preventDefault()) : e === n.last()[0] && (n.first().focus(), t.preventDefault())) |
||||
|
}) |
||||
|
}, 0)) |
||||
|
} |
||||
|
}), B.background = function(t, n, e, o) { |
||||
|
e === w && (e = 400), o = o || x.body, t ? A(y("<div/>").addClass(n).css("display", "none").insertAfter(o), e) : k(y("div." + n), e, function() { |
||||
|
y(this).removeClass(n).remove() |
||||
|
}) |
||||
|
}, B.instanceSelector = function(t, i) { |
||||
|
var s, a, r; |
||||
|
return t === w || null === t ? y.map(i, function(t) { |
||||
|
return t.inst |
||||
|
}) : (s = [], a = y.map(i, function(t) { |
||||
|
return t.name |
||||
|
}), (r = function(t) { |
||||
|
var n; |
||||
|
if (Array.isArray(t)) |
||||
|
for (var e = 0, o = t.length; e < o; e++) r(t[e]); |
||||
|
else "string" == typeof t ? -1 !== t.indexOf(",") ? r(t.split(",")) : -1 !== (n = y.inArray(t.trim(), a)) && s.push(i[n].inst) : "number" == typeof t ? s.push(i[t].inst) : "object" == typeof t && s.push(t) |
||||
|
})(t), s) |
||||
|
}, B.buttonSelector = function(t, n) { |
||||
|
for (var c = [], u = function(t, n, e) { |
||||
|
for (var o, i, s = 0, a = n.length; s < a; s++)(o = n[s]) && (t.push({ |
||||
|
node: o.node, |
||||
|
name: o.conf.name, |
||||
|
idx: i = e !== w ? e + s : s + "" |
||||
|
}), o.buttons) && u(t, o.buttons, i + "-") |
||||
|
}, d = function(t, n) { |
||||
|
var e = [], |
||||
|
o = (u(e, n.s.buttons), y.map(e, function(t) { |
||||
|
return t.node |
||||
|
})); |
||||
|
if (Array.isArray(t) || t instanceof y) |
||||
|
for (s = 0, a = t.length; s < a; s++) d(t[s], n); |
||||
|
else if (null === t || t === w || "*" === t) |
||||
|
for (s = 0, a = e.length; s < a; s++) c.push({ |
||||
|
inst: n, |
||||
|
node: e[s].node |
||||
|
}); |
||||
|
else if ("number" == typeof t) n.s.buttons[t] && c.push({ |
||||
|
inst: n, |
||||
|
node: n.s.buttons[t].node |
||||
|
}); |
||||
|
else if ("string" == typeof t) |
||||
|
if (-1 !== t.indexOf(",")) |
||||
|
for (var i = t.split(","), s = 0, a = i.length; s < a; s++) d(i[s].trim(), n); |
||||
|
else if (t.match(/^\d+(\-\d+)*$/)) { |
||||
|
var r = y.map(e, function(t) { |
||||
|
return t.idx |
||||
|
}); |
||||
|
c.push({ |
||||
|
inst: n, |
||||
|
node: e[y.inArray(t, r)].node |
||||
|
}) |
||||
|
} else if (-1 !== t.indexOf(":name")) { |
||||
|
var l = t.replace(":name", ""); |
||||
|
for (s = 0, a = e.length; s < a; s++) e[s].name === l && c.push({ |
||||
|
inst: n, |
||||
|
node: e[s].node |
||||
|
}) |
||||
|
} else y(o).filter(t).each(function() { |
||||
|
c.push({ |
||||
|
inst: n, |
||||
|
node: this |
||||
|
}) |
||||
|
}); |
||||
|
else "object" == typeof t && t.nodeName && -1 !== (r = y.inArray(t, o)) && c.push({ |
||||
|
inst: n, |
||||
|
node: o[r] |
||||
|
}) |
||||
|
}, e = 0, o = t.length; e < o; e++) { |
||||
|
var i = t[e]; |
||||
|
d(n, i) |
||||
|
} |
||||
|
return c |
||||
|
}, B.stripData = function(t, n) { |
||||
|
return "string" == typeof t && (t = (t = t.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).replace(/<!\-\-.*?\-\->/g, ""), n && !n.stripHtml || (t = t.replace(/<[^>]*>/g, "")), n && !n.trim || (t = t.replace(/^\s+|\s+$/g, "")), n && !n.stripNewlines || (t = t.replace(/\n/g, " ")), !n || n.decodeEntities) && (l.innerHTML = t, t = l.value), t |
||||
|
}, B.defaults = { |
||||
|
buttons: ["copy", "excel", "csv", "pdf", "print"], |
||||
|
name: "main", |
||||
|
tabIndex: 0, |
||||
|
dom: { |
||||
|
container: { |
||||
|
tag: "div", |
||||
|
className: "dt-buttons" |
||||
|
}, |
||||
|
collection: { |
||||
|
action: { |
||||
|
dropHtml: '<span class="dt-button-down-arrow">▼</span>' |
||||
|
}, |
||||
|
container: { |
||||
|
className: "dt-button-collection", |
||||
|
content: { |
||||
|
className: "", |
||||
|
tag: "div" |
||||
|
}, |
||||
|
tag: "div" |
||||
|
} |
||||
|
}, |
||||
|
button: { |
||||
|
tag: "button", |
||||
|
className: "dt-button", |
||||
|
active: "dt-button-active", |
||||
|
disabled: "disabled", |
||||
|
spacer: { |
||||
|
className: "dt-button-spacer", |
||||
|
tag: "span" |
||||
|
}, |
||||
|
liner: { |
||||
|
tag: "span", |
||||
|
className: "" |
||||
|
} |
||||
|
}, |
||||
|
split: { |
||||
|
action: { |
||||
|
className: "dt-button-split-drop-button dt-button", |
||||
|
tag: "button" |
||||
|
}, |
||||
|
dropdown: { |
||||
|
align: "split-right", |
||||
|
className: "dt-button-split-drop", |
||||
|
dropHtml: '<span class="dt-button-down-arrow">▼</span>', |
||||
|
splitAlignClass: "dt-button-split-left", |
||||
|
tag: "button" |
||||
|
}, |
||||
|
wrapper: { |
||||
|
className: "dt-button-split", |
||||
|
tag: "div" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, y.extend(_, { |
||||
|
collection: { |
||||
|
text: function(t) { |
||||
|
return t.i18n("buttons.collection", "Collection") |
||||
|
}, |
||||
|
className: "buttons-collection", |
||||
|
closeButton: !(B.version = "2.4.1"), |
||||
|
init: function(t, n, e) { |
||||
|
n.attr("aria-expanded", !1) |
||||
|
}, |
||||
|
action: function(t, n, e, o) { |
||||
|
o._collection.parents("body").length ? this.popover(!1, o) : this.popover(o._collection, o), "keypress" === t.type && y("a, button", o._collection).eq(0).focus() |
||||
|
}, |
||||
|
attr: { |
||||
|
"aria-haspopup": "dialog" |
||||
|
} |
||||
|
}, |
||||
|
split: { |
||||
|
text: function(t) { |
||||
|
return t.i18n("buttons.split", "Split") |
||||
|
}, |
||||
|
className: "buttons-split", |
||||
|
closeButton: !1, |
||||
|
init: function(t, n, e) { |
||||
|
return n.attr("aria-expanded", !1) |
||||
|
}, |
||||
|
action: function(t, n, e, o) { |
||||
|
this.popover(o._collection, o) |
||||
|
}, |
||||
|
attr: { |
||||
|
"aria-haspopup": "dialog" |
||||
|
} |
||||
|
}, |
||||
|
copy: function(t, n) { |
||||
|
if (_.copyHtml5) return "copyHtml5" |
||||
|
}, |
||||
|
csv: function(t, n) { |
||||
|
if (_.csvHtml5 && _.csvHtml5.available(t, n)) return "csvHtml5" |
||||
|
}, |
||||
|
excel: function(t, n) { |
||||
|
if (_.excelHtml5 && _.excelHtml5.available(t, n)) return "excelHtml5" |
||||
|
}, |
||||
|
pdf: function(t, n) { |
||||
|
if (_.pdfHtml5 && _.pdfHtml5.available(t, n)) return "pdfHtml5" |
||||
|
}, |
||||
|
pageLength: function(t) { |
||||
|
var n = t.settings()[0].aLengthMenu, |
||||
|
e = [], |
||||
|
o = []; |
||||
|
if (Array.isArray(n[0])) e = n[0], o = n[1]; |
||||
|
else |
||||
|
for (var i = 0; i < n.length; i++) { |
||||
|
var s = n[i]; |
||||
|
y.isPlainObject(s) ? (e.push(s.value), o.push(s.label)) : (e.push(s), o.push(s)) |
||||
|
} |
||||
|
return { |
||||
|
extend: "collection", |
||||
|
text: function(t) { |
||||
|
return t.i18n("buttons.pageLength", { |
||||
|
"-1": "Show all rows", |
||||
|
_: "Show %d rows" |
||||
|
}, t.page.len()) |
||||
|
}, |
||||
|
className: "buttons-page-length", |
||||
|
autoClose: !0, |
||||
|
buttons: y.map(e, function(s, t) { |
||||
|
return { |
||||
|
text: o[t], |
||||
|
className: "button-page-length", |
||||
|
action: function(t, n) { |
||||
|
n.page.len(s).draw() |
||||
|
}, |
||||
|
init: function(t, n, e) { |
||||
|
function o() { |
||||
|
i.active(t.page.len() === s) |
||||
|
} |
||||
|
var i = this; |
||||
|
t.on("length.dt" + e.namespace, o), o() |
||||
|
}, |
||||
|
destroy: function(t, n, e) { |
||||
|
t.off("length.dt" + e.namespace) |
||||
|
} |
||||
|
} |
||||
|
}), |
||||
|
init: function(t, n, e) { |
||||
|
var o = this; |
||||
|
t.on("length.dt" + e.namespace, function() { |
||||
|
o.text(e.text) |
||||
|
}) |
||||
|
}, |
||||
|
destroy: function(t, n, e) { |
||||
|
t.off("length.dt" + e.namespace) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
spacer: { |
||||
|
style: "empty", |
||||
|
spacer: !0, |
||||
|
text: function(t) { |
||||
|
return t.i18n("buttons.spacer", "") |
||||
|
} |
||||
|
} |
||||
|
}), e.Api.register("buttons()", function(n, e) { |
||||
|
e === w && (e = n, n = w), this.selector.buttonGroup = n; |
||||
|
var t = this.iterator(!0, "table", function(t) { |
||||
|
if (t._buttons) return B.buttonSelector(B.instanceSelector(n, t._buttons), e) |
||||
|
}, !0); |
||||
|
return t._groupSelector = n, t |
||||
|
}), e.Api.register("button()", function(t, n) { |
||||
|
t = this.buttons(t, n); |
||||
|
return 1 < t.length && t.splice(1, t.length), t |
||||
|
}), e.Api.registerPlural("buttons().active()", "button().active()", function(n) { |
||||
|
return n === w ? this.map(function(t) { |
||||
|
return t.inst.active(t.node) |
||||
|
}) : this.each(function(t) { |
||||
|
t.inst.active(t.node, n) |
||||
|
}) |
||||
|
}), e.Api.registerPlural("buttons().action()", "button().action()", function(n) { |
||||
|
return n === w ? this.map(function(t) { |
||||
|
return t.inst.action(t.node) |
||||
|
}) : this.each(function(t) { |
||||
|
t.inst.action(t.node, n) |
||||
|
}) |
||||
|
}), e.Api.registerPlural("buttons().collectionRebuild()", "button().collectionRebuild()", function(e) { |
||||
|
return this.each(function(t) { |
||||
|
for (var n = 0; n < e.length; n++) "object" == typeof e[n] && (e[n].parentConf = t); |
||||
|
t.inst.collectionRebuild(t.node, e) |
||||
|
}) |
||||
|
}), e.Api.register(["buttons().enable()", "button().enable()"], function(n) { |
||||
|
return this.each(function(t) { |
||||
|
t.inst.enable(t.node, n) |
||||
|
}) |
||||
|
}), e.Api.register(["buttons().disable()", "button().disable()"], function() { |
||||
|
return this.each(function(t) { |
||||
|
t.inst.disable(t.node) |
||||
|
}) |
||||
|
}), e.Api.register("button().index()", function() { |
||||
|
var n = null; |
||||
|
return this.each(function(t) { |
||||
|
t = t.inst.index(t.node); |
||||
|
null !== t && (n = t) |
||||
|
}), n |
||||
|
}), e.Api.registerPlural("buttons().nodes()", "button().node()", function() { |
||||
|
var n = y(); |
||||
|
return y(this.each(function(t) { |
||||
|
n = n.add(t.inst.node(t.node)) |
||||
|
})), n |
||||
|
}), e.Api.registerPlural("buttons().processing()", "button().processing()", function(n) { |
||||
|
return n === w ? this.map(function(t) { |
||||
|
return t.inst.processing(t.node) |
||||
|
}) : this.each(function(t) { |
||||
|
t.inst.processing(t.node, n) |
||||
|
}) |
||||
|
}), e.Api.registerPlural("buttons().text()", "button().text()", function(n) { |
||||
|
return n === w ? this.map(function(t) { |
||||
|
return t.inst.text(t.node) |
||||
|
}) : this.each(function(t) { |
||||
|
t.inst.text(t.node, n) |
||||
|
}) |
||||
|
}), e.Api.registerPlural("buttons().trigger()", "button().trigger()", function() { |
||||
|
return this.each(function(t) { |
||||
|
t.inst.node(t.node).trigger("click") |
||||
|
}) |
||||
|
}), e.Api.register("button().popover()", function(n, e) { |
||||
|
return this.map(function(t) { |
||||
|
return t.inst._popover(n, this.button(this[0].node), e) |
||||
|
}) |
||||
|
}), e.Api.register("buttons().containers()", function() { |
||||
|
var i = y(), |
||||
|
s = this._groupSelector; |
||||
|
return this.iterator(!0, "table", function(t) { |
||||
|
if (t._buttons) |
||||
|
for (var n = B.instanceSelector(s, t._buttons), e = 0, o = n.length; e < o; e++) i = i.add(n[e].container()) |
||||
|
}), i |
||||
|
}), e.Api.register("buttons().container()", function() { |
||||
|
return this.containers().eq(0) |
||||
|
}), e.Api.register("button().add()", function(t, n, e) { |
||||
|
var o = this.context; |
||||
|
return o.length && (o = B.instanceSelector(this._groupSelector, o[0]._buttons)).length && o[0].add(n, t, e), this.button(this._groupSelector, t) |
||||
|
}), e.Api.register("buttons().destroy()", function() { |
||||
|
return this.pluck("inst").unique().each(function(t) { |
||||
|
t.destroy() |
||||
|
}), this |
||||
|
}), e.Api.registerPlural("buttons().remove()", "buttons().remove()", function() { |
||||
|
return this.each(function(t) { |
||||
|
t.inst.remove(t.node) |
||||
|
}), this |
||||
|
}), e.Api.register("buttons.info()", function(t, n, e) { |
||||
|
var o = this; |
||||
|
return !1 === t ? (this.off("destroy.btn-info"), k(y("#datatables_buttons_info"), 400, function() { |
||||
|
y(this).remove() |
||||
|
}), clearTimeout(i), i = null) : (i && clearTimeout(i), y("#datatables_buttons_info").length && y("#datatables_buttons_info").remove(), t = t ? "<h2>" + t + "</h2>" : "", A(y('<div id="datatables_buttons_info" class="dt-button-info"/>').html(t).append(y("<div/>")["string" == typeof n ? "html" : "append"](n)).css("display", "none").appendTo("body")), e !== w && 0 !== e && (i = setTimeout(function() { |
||||
|
o.buttons.info(!1) |
||||
|
}, e)), this.on("destroy.btn-info", function() { |
||||
|
o.buttons.info(!1) |
||||
|
})), this |
||||
|
}), e.Api.register("buttons.exportData()", function(t) { |
||||
|
if (this.context.length) return c(new e.Api(this.context[0]), t) |
||||
|
}), e.Api.register("buttons.exportInfo()", function(t) { |
||||
|
return { |
||||
|
filename: n(t = t || {}), |
||||
|
title: a(t), |
||||
|
messageTop: r(this, t.message || t.messageTop, "top"), |
||||
|
messageBottom: r(this, t.messageBottom, "bottom") |
||||
|
} |
||||
|
}); |
||||
|
var i, n = function(t) { |
||||
|
var n; |
||||
|
return (n = "function" == typeof (n = "*" === t.filename && "*" !== t.title && t.title !== w && null !== t.title && "" !== t.title ? t.title : t.filename) ? n() : n) === w || null === n ? null : (n = (n = -1 !== n.indexOf("*") ? n.replace("*", y("head > title").text()).trim() : n).replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "")) + (s(t.extension) || "") |
||||
|
}, |
||||
|
s = function(t) { |
||||
|
return null === t || t === w ? null : "function" == typeof t ? t() : t |
||||
|
}, |
||||
|
a = function(t) { |
||||
|
t = s(t.title); |
||||
|
return null === t ? null : -1 !== t.indexOf("*") ? t.replace("*", y("head > title").text() || "Exported data") : t |
||||
|
}, |
||||
|
r = function(t, n, e) { |
||||
|
n = s(n); |
||||
|
return null === n ? null : (t = y("caption", t.table().container()).eq(0), "*" === n ? t.css("caption-side") !== e ? null : t.length ? t.text() : "" : n) |
||||
|
}, |
||||
|
l = y("<textarea/>")[0], |
||||
|
c = function(e, t) { |
||||
|
for (var o = y.extend(!0, {}, { |
||||
|
rows: null, |
||||
|
columns: "", |
||||
|
modifier: { |
||||
|
search: "applied", |
||||
|
order: "applied" |
||||
|
}, |
||||
|
orthogonal: "display", |
||||
|
stripHtml: !0, |
||||
|
stripNewlines: !0, |
||||
|
decodeEntities: !0, |
||||
|
trim: !0, |
||||
|
format: { |
||||
|
header: function(t) { |
||||
|
return B.stripData(t, o) |
||||
|
}, |
||||
|
footer: function(t) { |
||||
|
return B.stripData(t, o) |
||||
|
}, |
||||
|
body: function(t) { |
||||
|
return B.stripData(t, o) |
||||
|
} |
||||
|
}, |
||||
|
customizeData: null |
||||
|
}, t), t = e.columns(o.columns).indexes().map(function(t) { |
||||
|
var n = e.column(t).header(); |
||||
|
return o.format.header(n.innerHTML, t, n) |
||||
|
}).toArray(), n = e.table().footer() ? e.columns(o.columns).indexes().map(function(t) { |
||||
|
var n = e.column(t).footer(); |
||||
|
return o.format.footer(n ? n.innerHTML : "", t, n) |
||||
|
}).toArray() : null, i = y.extend({}, o.modifier), i = (e.select && "function" == typeof e.select.info && i.selected === w && e.rows(o.rows, y.extend({ |
||||
|
selected: !0 |
||||
|
}, i)).any() && y.extend(i, { |
||||
|
selected: !0 |
||||
|
}), e.rows(o.rows, i).indexes().toArray()), i = e.cells(i, o.columns), s = i.render(o.orthogonal).toArray(), a = i.nodes().toArray(), r = t.length, l = [], c = 0, u = 0, d = 0 < r ? s.length / r : 0; u < d; u++) { |
||||
|
for (var f = [r], p = 0; p < r; p++) f[p] = o.format.body(s[c], u, p, a[c]), c++; |
||||
|
l[u] = f |
||||
|
} |
||||
|
i = { |
||||
|
header: t, |
||||
|
footer: n, |
||||
|
body: l |
||||
|
}; |
||||
|
return o.customizeData && o.customizeData(i), i |
||||
|
}; |
||||
|
|
||||
|
function t(t, n) { |
||||
|
t = new e.Api(t), n = n || t.init().buttons || e.defaults.buttons; |
||||
|
return new B(t, n).container() |
||||
|
} |
||||
|
return y.fn.dataTable.Buttons = B, y.fn.DataTable.Buttons = B, y(x).on("init.dt plugin-init.dt", function(t, n) { |
||||
|
"dt" === t.namespace && (t = n.oInit.buttons || e.defaults.buttons) && !n._buttons && new B(n, t).container() |
||||
|
}), e.ext.feature.push({ |
||||
|
fnInit: t, |
||||
|
cFeature: "B" |
||||
|
}), e.ext.features && e.ext.features.register("buttons", t), e |
||||
|
}); |
||||
6995
src/main/resources/static/assets/font-awesome-6.5.1/css/all.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
7023
src/main/resources/static/assets/font-awesome-6.5.2/css/all.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
|
After Width: 325 | Height: 435 | Size: 204 KiB |
3244
src/main/resources/static/assets/jquery-3.6.4/js/jquery-3.6.4.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,93 @@ |
|||||
|
// Global flags
|
||||
|
let otpSent = false; |
||||
|
let isAdmin = false; |
||||
|
|
||||
|
async function loadRole() { |
||||
|
try { |
||||
|
const res = await fetch(`${contextPath}api/get-role`, { |
||||
|
method: "POST" |
||||
|
}); |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
|
||||
|
if (!data.authenticated) { |
||||
|
isAdmin = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
isAdmin = data.isAdmin; |
||||
|
|
||||
|
} catch (e) { |
||||
|
// swallow network errors
|
||||
|
console.error("Error: ", error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Open the login modal
|
||||
|
function openLoginModal() { |
||||
|
if (isAdmin) { |
||||
|
// For admin → directly go to the post-verification page
|
||||
|
window.open(`${contextPath}view-login-det`, '_blank'); |
||||
|
return; |
||||
|
} |
||||
|
const modal = document.getElementById("login-modal"); |
||||
|
modal.classList.remove("hide"); |
||||
|
modal.classList.add("show"); |
||||
|
modal.style.display = "flex"; // make it visible immediately before animation
|
||||
|
} |
||||
|
|
||||
|
// Load role when page loads
|
||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||
|
loadRole(); |
||||
|
}); |
||||
|
|
||||
|
document.addEventListener("DOMContentLoaded", function() { |
||||
|
const togglePass = document.getElementById("togglePass"); |
||||
|
const urlUsrPassInput = document.getElementById("urlDataUsrPass"); |
||||
|
const form = document.getElementById("frm-url-det"); |
||||
|
|
||||
|
// safety check
|
||||
|
if (!togglePass || !urlUsrPassInput) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// if (!urlUsrPassInput) return;
|
||||
|
|
||||
|
// --- Toggle Password Visibility ---
|
||||
|
togglePass.addEventListener("click", function() { |
||||
|
const isPassword = urlUsrPassInput.type === "password"; |
||||
|
urlUsrPassInput.type = isPassword ? "text" : "password"; |
||||
|
togglePass.classList.toggle("fa-eye"); |
||||
|
togglePass.classList.toggle("fa-eye-slash"); |
||||
|
}); |
||||
|
|
||||
|
// --- Form submission logic ---
|
||||
|
form.addEventListener("submit", function(e) { |
||||
|
e.preventDefault(); |
||||
|
|
||||
|
const formData = new FormData(form); |
||||
|
|
||||
|
fetch(form.getAttribute("action"), { |
||||
|
method: "POST", |
||||
|
body: formData |
||||
|
}) |
||||
|
.then(response => { |
||||
|
if (!response.ok) { |
||||
|
throw new Error("Network response was not ok: " + response.statusText); |
||||
|
} |
||||
|
return response.json(); |
||||
|
}) |
||||
|
.then(data => { |
||||
|
if (data.resStat) { |
||||
|
alert("Success: " + data.res); |
||||
|
form.reset(); |
||||
|
} else { |
||||
|
alert("Failed: " + data.res); |
||||
|
} |
||||
|
}) |
||||
|
.catch(error => { |
||||
|
console.error("Error adding url:", error); |
||||
|
alert("Error adding url. Please try again."); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,23 @@ |
|||||
|
const csrfMeta = document.querySelector('meta[name="_csrf"]'); |
||||
|
const csrfHeaderMeta = document.querySelector('meta[name="_csrf_header"]'); |
||||
|
|
||||
|
const csrfToken = csrfMeta ? csrfMeta.content : null; |
||||
|
const csrfHeader = csrfHeaderMeta ? csrfHeaderMeta.content : null; |
||||
|
|
||||
|
// Save original fetch
|
||||
|
const originalFetch = window.fetch; |
||||
|
|
||||
|
window.fetch = (url, options = {}) => { |
||||
|
// Default method is GET if not provided
|
||||
|
const method = (options.method || "GET").toUpperCase(); |
||||
|
|
||||
|
// Only attach CSRF for unsafe methods
|
||||
|
if (["POST", "PUT", "DELETE", "PATCH"].includes(method)) { |
||||
|
options.headers = { |
||||
|
...options.headers, |
||||
|
[csrfHeader]: csrfToken |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return originalFetch(url, options); |
||||
|
}; |
||||
@ -0,0 +1,3 @@ |
|||||
|
function goBack() { |
||||
|
window.history.back(); |
||||
|
} |
||||
@ -0,0 +1,426 @@ |
|||||
|
// Global flags
|
||||
|
let otpSent = false; |
||||
|
var isAdmin = window.isAdmin || false; |
||||
|
|
||||
|
async function clearBackendOtpSession() { |
||||
|
try { |
||||
|
await fetch(`${contextPath}api/clr-otp-ses`, { method: "POST" }); |
||||
|
} catch (e) { |
||||
|
console.error("Failed to clear backend OTP session", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function reloadMobileDropdown() { |
||||
|
try { |
||||
|
const res = await fetch(`${contextPath}api/get-mob-num`, { |
||||
|
method: "POST", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json" |
||||
|
}, |
||||
|
body: "{}" // empty JSON payload
|
||||
|
}); |
||||
|
|
||||
|
const mobNumInput = document.getElementById("mobNum"); |
||||
|
mobNumInput.innerHTML = ""; // clear all old options
|
||||
|
|
||||
|
if (!res.ok) { |
||||
|
mobNumInput.innerHTML = '<option value="">-- Failed to load numbers --</option>'; |
||||
|
console.error(`Failed to fetch mobile numbers: ${res.status} ${res.statusText}`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (res.status === 204) { |
||||
|
// No data returned
|
||||
|
mobNumInput.innerHTML = '<option value="">-- No mobile numbers available --</option>'; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
data.forEach(item => { |
||||
|
const opt = document.createElement("option"); |
||||
|
opt.value = item.mobNumId; // new UUID
|
||||
|
opt.textContent = item.mobNum; // masked number
|
||||
|
mobNumInput.appendChild(opt); |
||||
|
}); |
||||
|
|
||||
|
} catch (err) { |
||||
|
console.error("Error loading mobile numbers:", err); |
||||
|
const mobNumInput = document.getElementById("mobNum"); |
||||
|
mobNumInput.innerHTML = '<option value="">-- Failed to load numbers --</option>'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function loadRole() { |
||||
|
try { |
||||
|
const res = await fetch(`${contextPath}api/get-role`, { |
||||
|
method: "POST" |
||||
|
}); |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
|
||||
|
if (!data.authenticated) { |
||||
|
isAdmin = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
isAdmin = data.isAdmin; |
||||
|
|
||||
|
} catch (e) { |
||||
|
// swallow network errors
|
||||
|
console.error("Error: ", error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function loadMobNum() { |
||||
|
try { |
||||
|
const res = await fetch(`${contextPath}api/get-mob-num`, { |
||||
|
method: "POST", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json" |
||||
|
}, |
||||
|
body: "{}" |
||||
|
}); |
||||
|
|
||||
|
const mobSelect = document.getElementById("mobNum"); |
||||
|
mobSelect.innerHTML = '<option value="">-- Select Mobile Number --</option>'; |
||||
|
|
||||
|
if (res.status === 204) { |
||||
|
// No data returned
|
||||
|
mobSelect.innerHTML = '<option value="">-- No mobile numbers available --</option>'; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!res.ok) { |
||||
|
throw new Error(`Server error: ${res.status}`); |
||||
|
} |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
data.forEach(item => { |
||||
|
const opt = document.createElement("option"); |
||||
|
opt.value = item.mobNumId; // mobNumId for backend
|
||||
|
opt.textContent = item.mobNum; // Masked number for display
|
||||
|
mobSelect.appendChild(opt); |
||||
|
}); |
||||
|
|
||||
|
} catch (err) { |
||||
|
console.error("Failed to load mobile numbers", err); |
||||
|
const mobSelect = document.getElementById("mobNum"); |
||||
|
mobSelect.innerHTML = '<option value="">-- Failed to load numbers --</option>'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Open the login modal
|
||||
|
function openLoginModal() { |
||||
|
if (isAdmin) { |
||||
|
// For admin → directly go to the post-verification page
|
||||
|
window.open(`${contextPath}view-login-det`, '_blank'); |
||||
|
return; |
||||
|
} |
||||
|
const modal = document.getElementById("login-modal"); |
||||
|
modal.classList.remove("hide"); |
||||
|
modal.classList.add("show"); |
||||
|
modal.style.display = "flex"; // make it visible immediately before animation
|
||||
|
|
||||
|
// Load only when modal is opened
|
||||
|
loadMobNum(); |
||||
|
} |
||||
|
|
||||
|
// Close the modal and reset its state
|
||||
|
async function closeLoginModal() { |
||||
|
const modal = document.getElementById("login-modal"); |
||||
|
const otpForm = document.getElementById("otp-form"); |
||||
|
const otpSection = document.getElementById("otp-section"); |
||||
|
const sendOtpBtn = document.getElementById("send-otp-btn"); |
||||
|
const messageEl = document.getElementById("otp-message"); |
||||
|
const mobInput = document.getElementById("mobNum"); |
||||
|
const otpInput = document.getElementById("otp"); |
||||
|
|
||||
|
// Reset form and state
|
||||
|
if (otpForm) otpForm.reset(); // Reset the form fields
|
||||
|
if (otpSection) otpSection.style.display = "none"; // Hide OTP section
|
||||
|
if (sendOtpBtn) { |
||||
|
sendOtpBtn.textContent = "Get OTP"; // Reset button text
|
||||
|
sendOtpBtn.disabled = false; // Enable the button
|
||||
|
} |
||||
|
if (messageEl) { |
||||
|
messageEl.textContent = ""; // Clear any message
|
||||
|
messageEl.style.color = "#333"; // Reset the default message color
|
||||
|
} |
||||
|
|
||||
|
// Reset mobile number input and OTP input
|
||||
|
if (mobInput) mobInput.value = ''; |
||||
|
if (otpInput) otpInput.value = ''; |
||||
|
|
||||
|
// Reset JS flag
|
||||
|
otpSent = false; |
||||
|
|
||||
|
await clearBackendOtpSession(); |
||||
|
|
||||
|
// Animate and hide the modal
|
||||
|
modal.classList.remove("show"); |
||||
|
modal.classList.add("hide"); |
||||
|
|
||||
|
// Handle the animation and hide modal after animation ends
|
||||
|
modal.addEventListener( |
||||
|
"animationend", |
||||
|
function handler() { |
||||
|
if (modal.classList.contains("hide")) { |
||||
|
modal.style.display = "none"; // Hide modal after animation
|
||||
|
} |
||||
|
modal.removeEventListener("animationend", handler); // Clean up the event listener
|
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// Close the modal when clicking outside of it
|
||||
|
window.onclick = function (event) { |
||||
|
const modal = document.getElementById("login-modal"); |
||||
|
if (event.target === modal) { |
||||
|
closeLoginModal(); // Also reset everything when clicking outside
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Load role when page loads
|
||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||
|
loadRole(); |
||||
|
}); |
||||
|
|
||||
|
// OTP Form Submission Handling
|
||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||
|
const otpForm = document.getElementById("otp-form"); |
||||
|
if (!otpForm) return; |
||||
|
|
||||
|
const messageEl = document.getElementById("otp-message"); |
||||
|
const otpSection = document.getElementById("otp-section"); |
||||
|
const sendOtpBtn = document.getElementById("send-otp-btn"); |
||||
|
const mobNumInput = document.getElementById("mobNum"); |
||||
|
const otpInput = document.getElementById("otp"); |
||||
|
|
||||
|
otpForm.addEventListener("submit", async (e) => { |
||||
|
e.preventDefault(); |
||||
|
messageEl.textContent = ""; |
||||
|
messageEl.style.color = "inherit"; |
||||
|
|
||||
|
const mobId = mobNumInput.value; // mobNumId
|
||||
|
const selectedText = mobNumInput.options[mobNumInput.selectedIndex]?.text; |
||||
|
|
||||
|
if (!mobId) { |
||||
|
messageEl.textContent = "Please select a mobile number."; |
||||
|
messageEl.style.color = "red"; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
sendOtpBtn.disabled = true; |
||||
|
|
||||
|
try { |
||||
|
if (!otpSent) { |
||||
|
// === GET OTP PHASE ===
|
||||
|
sendOtpBtn.textContent = "Sending..."; |
||||
|
|
||||
|
const formData = new FormData(); |
||||
|
formData.append("mobNumId", mobId); |
||||
|
|
||||
|
const res = await fetch(`${contextPath}api/get-otp`, { |
||||
|
method: "POST", |
||||
|
body: formData, // sends as multipart/form-data
|
||||
|
}); |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
|
||||
|
if (res.ok && data.status === "success") { |
||||
|
messageEl.textContent = data.message || "OTP sent successfully!"; |
||||
|
messageEl.style.color = "green"; |
||||
|
otpSection.style.display = "block"; |
||||
|
sendOtpBtn.textContent = "Verify OTP"; |
||||
|
otpSent = true; |
||||
|
} else { |
||||
|
// backend may respond with 400 or 500
|
||||
|
messageEl.textContent = data.message || "Failed to send OTP."; |
||||
|
messageEl.style.color = "red"; |
||||
|
sendOtpBtn.textContent = "Get OTP"; |
||||
|
} |
||||
|
|
||||
|
} else { |
||||
|
// === VERIFY OTP PHASE ===
|
||||
|
const enteredOtp = otpInput.value.trim(); |
||||
|
if (!enteredOtp) { |
||||
|
messageEl.textContent = "Please enter the OTP."; |
||||
|
messageEl.style.color = "red"; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
sendOtpBtn.textContent = "Verifying..."; |
||||
|
|
||||
|
const formData = new FormData(); |
||||
|
formData.append("mobNumId", mobId); |
||||
|
formData.append("otp", enteredOtp); |
||||
|
|
||||
|
const res = await fetch(`${contextPath}api/verify-otp`, { |
||||
|
method: "POST", |
||||
|
body: formData, // sends as multipart/form-data
|
||||
|
}); |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
|
||||
|
if (res.ok && data.resStat) { |
||||
|
// OTP verified successfully
|
||||
|
messageEl.textContent = data.res || "OTP verified successfully!"; |
||||
|
messageEl.style.color = "green"; |
||||
|
otpSection.style.display = "none"; |
||||
|
sendOtpBtn.disabled = true; |
||||
|
|
||||
|
await reloadMobileDropdown(); |
||||
|
|
||||
|
// Close modal, then redirect to home page
|
||||
|
setTimeout(() => { |
||||
|
closeLoginModal(); |
||||
|
window.location.href = contextPath; |
||||
|
}, 500); |
||||
|
|
||||
|
} else { |
||||
|
// OTP verification failed or session expired
|
||||
|
messageEl.textContent = data.res || "OTP verification failed. Please request a new one."; |
||||
|
messageEl.style.color = "red"; |
||||
|
sendOtpBtn.textContent = "Get OTP"; |
||||
|
otpSection.style.display = "none"; // hide OTP field
|
||||
|
otpSent = false; // reset flag → next click sends a new OTP
|
||||
|
otpInput.value = ""; // clear OTP field only
|
||||
|
|
||||
|
await reloadMobileDropdown(); |
||||
|
|
||||
|
// If session expired, allow new OTP request
|
||||
|
if (data.res && data.res.includes("Session expired")) { |
||||
|
mobNumInput.disabled = false; // ensure user can still use the same number
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (err) { |
||||
|
console.error("Error:", err); |
||||
|
messageEl.textContent = "Something went wrong. Please try again."; |
||||
|
messageEl.style.color = "red"; |
||||
|
sendOtpBtn.textContent = otpSent ? "Verify OTP" : "Get OTP"; |
||||
|
} finally { |
||||
|
sendOtpBtn.disabled = false; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
/* === PAGE BUTTON FILTER & SEARCH === */ |
||||
|
document.addEventListener("DOMContentLoaded", function () { |
||||
|
const buttonContainer = document.querySelector(".button-row"); |
||||
|
if (!buttonContainer) return; |
||||
|
|
||||
|
const buttons = Array.from(buttonContainer.querySelectorAll(".custom-button")); |
||||
|
|
||||
|
function getButtonName(btn) { |
||||
|
return btn.childNodes[btn.childNodes.length - 1].textContent.trim().toLowerCase(); |
||||
|
} |
||||
|
|
||||
|
const searchInput = document.getElementById("search-input"); |
||||
|
const alphabetSelect = document.getElementById("search-input2"); |
||||
|
const clearBtn = document.getElementById("clear-btn"); |
||||
|
const noResultsMsg = document.getElementById("no-res-msg"); |
||||
|
|
||||
|
if (!searchInput || !alphabetSelect) return; |
||||
|
|
||||
|
/* === Reusable Dropdown Renderer === */ |
||||
|
function populateAlphabetDropdown(onlyVisible = false) { |
||||
|
const prevSelected = alphabetSelect.value; // keep previous selection
|
||||
|
|
||||
|
// Reset dropdown
|
||||
|
alphabetSelect.innerHTML = '<option value="">-</option>'; |
||||
|
|
||||
|
const letters = new Set(); |
||||
|
|
||||
|
buttons.forEach(btn => { |
||||
|
if (onlyVisible && btn.style.display === "none") return; |
||||
|
|
||||
|
const name = getButtonName(btn); |
||||
|
if (name.length > 0) { |
||||
|
letters.add(name[0].toUpperCase()); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const sortedLetters = [...letters].sort(); |
||||
|
|
||||
|
sortedLetters.forEach(letter => { |
||||
|
const opt = document.createElement("option"); |
||||
|
opt.value = letter; |
||||
|
opt.textContent = letter; |
||||
|
alphabetSelect.appendChild(opt); |
||||
|
}); |
||||
|
|
||||
|
// Restore previous selection if still valid
|
||||
|
if (letters.has(prevSelected.toUpperCase())) { |
||||
|
alphabetSelect.value = prevSelected.toUpperCase(); |
||||
|
} else { |
||||
|
alphabetSelect.value = ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Initial dropdown load (all buttons)
|
||||
|
populateAlphabetDropdown(false); |
||||
|
|
||||
|
/* === FILTER BUTTONS === */ |
||||
|
function updateButtonVisibility() { |
||||
|
const query = searchInput.value.trim().toLowerCase(); |
||||
|
const selectedLetter = alphabetSelect.value.toLowerCase(); |
||||
|
|
||||
|
let matchedButtons = 0; |
||||
|
|
||||
|
buttons.forEach(btn => { |
||||
|
const name = getButtonName(btn); |
||||
|
|
||||
|
const matchesLetter = |
||||
|
selectedLetter === "" || name.startsWith(selectedLetter); |
||||
|
|
||||
|
const matchesSearch = |
||||
|
query === "" || name.includes(query); |
||||
|
|
||||
|
if (matchesLetter && matchesSearch) { |
||||
|
btn.style.display = "inline-block"; |
||||
|
matchedButtons++; |
||||
|
} else { |
||||
|
btn.style.display = "none"; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
noResultsMsg.style.display = matchedButtons > 0 ? "none" : "block"; |
||||
|
|
||||
|
// Show or hide clear button
|
||||
|
clearBtn.style.display = |
||||
|
(searchInput.value.trim() !== "" || alphabetSelect.value !== "") |
||||
|
? "inline-block" |
||||
|
: "none"; |
||||
|
|
||||
|
// Rebuild dropdown based on visible buttons
|
||||
|
populateAlphabetDropdown(true); |
||||
|
} |
||||
|
|
||||
|
/* === CLEAR BUTTON HANDLER === */ |
||||
|
clearBtn.addEventListener("click", function () { |
||||
|
searchInput.value = ""; |
||||
|
alphabetSelect.value = ""; |
||||
|
|
||||
|
clearBtn.style.display = "none"; |
||||
|
|
||||
|
// Reset all buttons visible
|
||||
|
buttons.forEach(btn => btn.style.display = "inline-block"); |
||||
|
|
||||
|
// Rebuild dropdown from all buttons
|
||||
|
populateAlphabetDropdown(false); |
||||
|
|
||||
|
noResultsMsg.style.display = "none"; |
||||
|
}); |
||||
|
|
||||
|
/* === EVENT LISTENERS === */ |
||||
|
alphabetSelect.addEventListener("change", updateButtonVisibility); |
||||
|
searchInput.addEventListener("keyup", updateButtonVisibility); |
||||
|
|
||||
|
// Initial render
|
||||
|
updateButtonVisibility(); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
// ////////////////////////
|
||||
|
|
||||
@ -0,0 +1,28 @@ |
|||||
|
function handleLoginSubmit2(event) { |
||||
|
event.preventDefault(); // prevent normal form submission
|
||||
|
|
||||
|
const form = document.getElementById("frm-usr-login-det"); |
||||
|
const formData = new FormData(form); |
||||
|
|
||||
|
fetch(contextPath + 'api/val-app-usr-login', { |
||||
|
method: 'POST', |
||||
|
body: formData |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(data => { |
||||
|
if (data.result) { |
||||
|
let usrName = data.usrName && data.usrName.length > 0 ? data.usrName : null; |
||||
|
if (usrName) { |
||||
|
alert("Login Success"); |
||||
|
window.location.href = contextPath + 'redirect'; |
||||
|
} else { |
||||
|
alert("No role found for this user!"); |
||||
|
} |
||||
|
} else { |
||||
|
alert(data.error || "Login failed"); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error during login:", err); |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
// Global flags
|
||||
|
let otpSent = false; |
||||
|
let isAdmin = false; |
||||
|
|
||||
|
async function loadRole() { |
||||
|
try { |
||||
|
const res = await fetch(`${contextPath}api/get-role`, { |
||||
|
method: "POST" |
||||
|
}); |
||||
|
|
||||
|
const data = await res.json(); |
||||
|
|
||||
|
if (!data.authenticated) { |
||||
|
isAdmin = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
isAdmin = data.isAdmin; |
||||
|
|
||||
|
} catch (e) { |
||||
|
// swallow network errors
|
||||
|
console.error("Error: ", error); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Open the login modal
|
||||
|
function openLoginModal() { |
||||
|
if (isAdmin) { |
||||
|
// For admin → directly go to the post-verification page
|
||||
|
window.open(`${contextPath}view-login-det`, '_blank'); |
||||
|
return; |
||||
|
} |
||||
|
const modal = document.getElementById("login-modal"); |
||||
|
modal.classList.remove("hide"); |
||||
|
modal.classList.add("show"); |
||||
|
modal.style.display = "flex"; // make it visible immediately before animation
|
||||
|
} |
||||
|
|
||||
|
// Load role when page loads
|
||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||
|
loadRole(); |
||||
|
}); |
||||
|
|
||||
|
document.addEventListener("DOMContentLoaded", function() { |
||||
|
const dropdown = document.getElementById("urlDropdown"); |
||||
|
const urlStatDropdown = document.getElementById("urlStatDropdown"); |
||||
|
const urlLinkInput = document.getElementById("urlDataLink"); |
||||
|
const urlNameInput = document.getElementById("urlDataName"); |
||||
|
const urlUsrNameInput = document.getElementById("urlDataUsrName"); |
||||
|
const urlUsrPassInput = document.getElementById("urlDataUsrPass"); |
||||
|
const urlCatNameInput = document.getElementById("urlCatName"); |
||||
|
const togglePass = document.getElementById("togglePass"); |
||||
|
|
||||
|
// safety check
|
||||
|
if (!dropdown || !togglePass) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// if (!dropdown) return;
|
||||
|
|
||||
|
// --- Dropdown change handler ---
|
||||
|
dropdown.addEventListener("change", function() { |
||||
|
const selected = dropdown.options[dropdown.selectedIndex]; |
||||
|
|
||||
|
if (!selected.value) { |
||||
|
urlLinkInput.value = ""; |
||||
|
urlNameInput.value = ""; |
||||
|
urlUsrNameInput.value = ""; |
||||
|
urlUsrPassInput.value = ""; |
||||
|
urlCatNameInput.value = ""; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const statId = selected.getAttribute("data-statId"); |
||||
|
if (statId) { |
||||
|
urlStatDropdown.value = statId; |
||||
|
} |
||||
|
|
||||
|
const link = selected.getAttribute("data-link"); |
||||
|
const name = selected.getAttribute("data-name"); |
||||
|
const usrname = selected.getAttribute("data-usrname"); |
||||
|
const pass = selected.getAttribute("data-pass"); |
||||
|
const urlCatName = selected.getAttribute("data-urlCatName"); |
||||
|
|
||||
|
urlLinkInput.value = link || ""; |
||||
|
urlNameInput.value = name || ""; |
||||
|
urlUsrNameInput.value = (usrname && usrname !== "-") ? usrname : ""; |
||||
|
urlUsrPassInput.value = (pass && pass !== "-") ? pass : ""; |
||||
|
urlCatNameInput.value = (urlCatName && urlCatName !== "-") ? urlCatName : ""; |
||||
|
|
||||
|
// Reset to hidden state
|
||||
|
urlUsrPassInput.type = "password"; |
||||
|
togglePass.classList.add("fa-eye"); |
||||
|
togglePass.classList.remove("fa-eye-slash"); |
||||
|
}); |
||||
|
|
||||
|
// --- Toggle Password Visibility ---
|
||||
|
togglePass.addEventListener("click", function() { |
||||
|
const isPassword = urlUsrPassInput.type === "password"; |
||||
|
urlUsrPassInput.type = isPassword ? "text" : "password"; |
||||
|
togglePass.classList.toggle("fa-eye"); |
||||
|
togglePass.classList.toggle("fa-eye-slash"); |
||||
|
}); |
||||
|
|
||||
|
const form = document.getElementById("frm-url-det"); |
||||
|
|
||||
|
// Existing form submission logic...
|
||||
|
form.addEventListener("submit", function(e) { |
||||
|
e.preventDefault(); // Prevent default form submission
|
||||
|
|
||||
|
const formData = new FormData(form); |
||||
|
|
||||
|
fetch(form.getAttribute("action"), { |
||||
|
method: "POST", |
||||
|
body: formData |
||||
|
}) |
||||
|
.then(response => { |
||||
|
if (!response.ok) { |
||||
|
throw new Error("Network response was not ok: " + response.statusText); |
||||
|
} |
||||
|
return response.json(); |
||||
|
}) |
||||
|
.then(data => { |
||||
|
if (data.resStat) { |
||||
|
alert("Success: " + data.res); |
||||
|
form.reset(); |
||||
|
} else { |
||||
|
alert("Failed: " + data.res); |
||||
|
} |
||||
|
}) |
||||
|
.catch(error => { |
||||
|
console.error("Error updating url:", error); |
||||
|
alert("Error updating url. Please try again."); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,56 @@ |
|||||
|
document.addEventListener("DOMContentLoaded", function() { |
||||
|
// Auto-close window after 2 minutes (120000 ms)
|
||||
|
setTimeout(() => { |
||||
|
window.close(); |
||||
|
}, 120000); |
||||
|
|
||||
|
// Initialize DataTables
|
||||
|
const table = $("#url-det-tbl").DataTable({ |
||||
|
pageLength: -1, // default = show ALL rows
|
||||
|
lengthMenu: [ |
||||
|
[5, 10, 20, 50, -1], // values
|
||||
|
[5, 10, 20, 50, "All"] // labels
|
||||
|
], |
||||
|
ordering: false, // Disable client-side sorting
|
||||
|
language: { |
||||
|
info: "Showing _START_ to _END_ of _TOTAL_ entries", |
||||
|
lengthMenu: "Show _MENU_ entries" |
||||
|
}, |
||||
|
dom: 'lfrtip', // hides default search bar
|
||||
|
|
||||
|
// Auto-insert S.No
|
||||
|
drawCallback: function(settings) { |
||||
|
let api = this.api(); |
||||
|
api.rows({ page: 'current' }).every(function(rowIdx, tableLoop, rowLoop) { |
||||
|
let index = rowIdx + 1; |
||||
|
api.cell(rowIdx, 0).data(index); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Move DataTables elements to your custom divs
|
||||
|
$('#custom-search').html($('.dataTables_filter')); |
||||
|
$('#custom-pagination').html($('.dataTables_paginate')); |
||||
|
$('#custom-info').html($('.dataTables_info')); |
||||
|
$('#custom-length').html($('.dataTables_length')); |
||||
|
}); |
||||
|
|
||||
|
const toggleIcons = document.querySelectorAll(".toggle-pass"); |
||||
|
|
||||
|
toggleIcons.forEach(icon => { |
||||
|
icon.addEventListener("click", function() { |
||||
|
const targetId = this.getAttribute("data-target"); |
||||
|
const passwordElement = document.getElementById(targetId); |
||||
|
const originalPassword = this.getAttribute("data-pass"); |
||||
|
|
||||
|
if (passwordElement.textContent.includes("*")) { |
||||
|
// Show actual password
|
||||
|
passwordElement.textContent = originalPassword; |
||||
|
this.classList.replace("fa-eye", "fa-eye-slash"); |
||||
|
} else { |
||||
|
// Hide password
|
||||
|
passwordElement.textContent = "*".repeat(originalPassword.length); |
||||
|
this.classList.replace("fa-eye-slash", "fa-eye"); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
1123
src/main/resources/static/assets/popper-2.11.8/js/popper.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,21 @@ |
|||||
|
<th:block th:fragment="footer"> |
||||
|
<!-- jQuery library JS --> |
||||
|
<script th:src="@{/assets/jquery-3.6.4/js/jquery-3.6.4.min.js}" type="text/javascript"></script> |
||||
|
<!-- DataTables JS --> |
||||
|
<script th:src="@{/assets/datatables-1.13.6/js/jquery.dataTables.min.js}" type="text/javascript"></script> |
||||
|
<script th:src="@{/assets/datatables-1.13.6/js/dataTables.bootstrap5.min.js}" type="text/javascript"></script> |
||||
|
<!-- DataTables Buttons JS --> |
||||
|
<script th:src="@{/assets/datatables-buttons-2.4.1/js/dataTables.buttons.min.js}" type="text/javascript"></script> |
||||
|
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.bootstrap5.min.js}" type="text/javascript"></script> |
||||
|
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.html5.min.js}" type="text/javascript"></script> |
||||
|
<script th:src="@{/assets/datatables-buttons-2.4.1/js/buttons.print.min.js}" type="text/javascript"></script> |
||||
|
<!-- Bootstrap JS --> |
||||
|
<script th:src="@{/assets/popper-2.11.8/js/popper.min.js}" type="text/javascript"></script> |
||||
|
<script th:src="@{/assets/bootstrap-5.3.2/js/bootstrap.min.js}" type="text/javascript"></script> |
||||
|
<!-- CSRF sscript--> |
||||
|
<script th:src="@{/assets/js/fetch-csrf-script.js}" type="text/javascript" defer></script> |
||||
|
<!-- Custom JS --> |
||||
|
<script th:src="@{/assets/js/go-back-script.js}" type="text/javascript"></script> |
||||
|
<!-- SweetAlert2 --> |
||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
||||
|
</th:block> |
||||
@ -0,0 +1,22 @@ |
|||||
|
<th:block th:fragment="header"> |
||||
|
<!-- Awesome Font --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/font-awesome-6.5.2/css/all.min.css}" type="text/css"> |
||||
|
<!-- Bootstrap CSS --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/bootstrap-5.3.2/css/bootstrap.min.css}" type="text/css"> |
||||
|
<!-- CSS for Header --> |
||||
|
<link rel="stylesheet" th:href="@{/assets/css/header-styles.css}" type="text/css"> |
||||
|
<div class="header"> |
||||
|
<div class="logo-left"> |
||||
|
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation Logo"> |
||||
|
<div class="logo-text"> |
||||
|
<h2>GREATER</h2> |
||||
|
<h2>CHENNAI</h2> |
||||
|
<h2>CORPORATION</h2> |
||||
|
<p>பெருநகர சென்னை மாநகராட்சி</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="title"> |
||||
|
<h2>GCC IT Cell Outsourced Manpower Details</h2> |
||||
|
</div> |
||||
|
</div> |
||||
|
</th:block> |
||||
@ -0,0 +1,79 @@ |
|||||
|
<th:block th:fragment="navbar(curPage, isAdmin)"> |
||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light"> |
||||
|
<div class="container-fluid"> |
||||
|
<!-- Use container-fluid for full-width navbar --> |
||||
|
<!-- Toggle button --> |
||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" |
||||
|
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" |
||||
|
aria-label="Toggle navigation"> |
||||
|
<span class="navbar-toggler-icon"></span> |
||||
|
</button> |
||||
|
<!-- Navigation items on the left --> |
||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent"> |
||||
|
<ul class="navbar-nav me-auto"> |
||||
|
<!-- Superuser Navigation Items --> |
||||
|
<li class="nav-item"><a class="nav-link" th:href="@{/}" th:if="${curPage != 'home'}"> <i |
||||
|
class=" fa-solid fa-house"></i> |
||||
|
Home |
||||
|
</a></li> |
||||
|
<!-- Admin-only links, visible only if isAdmin is true --> |
||||
|
<th:block th:if="${isAdmin}"> |
||||
|
<li class="nav-item"> |
||||
|
<a class="nav-link" th:href="@{/?action=addEmployee}" |
||||
|
onclick="if (document.getElementById('addEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addEmployeeModal')).show(); return false; }"> |
||||
|
<i class="fa-solid fa-circle-plus"></i> Add Employee |
||||
|
</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a class="nav-link" th:href="@{/?action=updateEmployee}" |
||||
|
onclick="if (document.getElementById('updateEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('updateEmployeeModal')).show(); return false; }"> |
||||
|
<i class="fa-solid fa-file-pen"></i> Update Employee |
||||
|
</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a class="nav-link" th:href="@{/?action=deleteEmployee}" |
||||
|
onclick="if (document.getElementById('deleteEmployeeModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('deleteEmployeeModal')).show(); return false; }"> |
||||
|
<i class="fa-solid fa-trash-can"></i> Delete Employee |
||||
|
</a> |
||||
|
</li> |
||||
|
<li class="nav-item"> |
||||
|
<a class="nav-link" th:href="@{/?action=addVendorWorkOrder}" |
||||
|
onclick="if (document.getElementById('addVendorWorkOrderModal')) { bootstrap.Modal.getOrCreateInstance(document.getElementById('addVendorWorkOrderModal')).show(); return false; }"> |
||||
|
<i class="fa-solid fa-folder-plus"></i> Add Vendor / Work Order |
||||
|
</a> |
||||
|
</li> |
||||
|
</th:block> |
||||
|
<!-- <li class="nav-item"> |
||||
|
<a class="nav-link" href="#" onclick="openLoginModal(); return false;" |
||||
|
th:if="${curPage != 'view-login-det'}"> |
||||
|
<i class="fa-solid fa-gauge"></i> Manpower Dashboard |
||||
|
</a> |
||||
|
</li> --> |
||||
|
</ul> |
||||
|
<!--Navigation item on the right--> |
||||
|
<!-- Admin-only links, visible only if isAdmin is true --> |
||||
|
<th:block th:if="${isAdmin}"> |
||||
|
<ul class=" navbar-nav ms-auto" th:if="${curPage == 'home'}"> |
||||
|
<li class=" nav-item"> |
||||
|
<form th:action="@{/usr-logout}" method="post" class="m-0 p-0"> |
||||
|
<input th:if="${_csrf != null}" type="hidden" th:name="${_csrf.parameterName}" |
||||
|
th:value="${_csrf.token}" /> |
||||
|
<button type="submit" class="dropdown-item"> |
||||
|
<i class="fa-solid fa-right-from-bracket"></i> Logout |
||||
|
</button> |
||||
|
</form> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</th:block> |
||||
|
<th:block th:unless="${isAdmin}"> |
||||
|
<ul class="navbar-nav ms-auto" th:if="${curPage == 'home'}"> |
||||
|
<li class="nav-item"><a class="nav-link" th:href="@{/login}" th:if="${curPage == 'home'}"> <i |
||||
|
class="fa-solid fa-right-to-bracket"></i> |
||||
|
Login |
||||
|
</a></li> |
||||
|
</ul> |
||||
|
</th:block> |
||||
|
</div> |
||||
|
</div> |
||||
|
</nav> |
||||
|
</th:block> |
||||
3456
src/main/resources/templates/home/home.html
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,88 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html xmlns:th="http://www.thymeleaf.org" lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>GCC IT Cell Manpower Details</title> |
||||
|
|
||||
|
<!-- CSRF Tokens --> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" /> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" /> |
||||
|
|
||||
|
<!-- Include CSS --> |
||||
|
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/login-form-styles.css}"> |
||||
|
|
||||
|
<!-- Awesome Font --> |
||||
|
<link th:href="@{/assets/font-awesome-6.5.1/css/all.min.css}" rel="stylesheet" type="text/css"> |
||||
|
|
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<div class="container"> |
||||
|
<img th:src="@{/assets/header-images/gcc1.png}" alt="Greater Chennai Corporation" class="small-logo"> |
||||
|
<h3>GREATER CHENNAI CORPORATION</h3> |
||||
|
<h4>பெருநகர சென்னை மாநகராட்சி</h4> |
||||
|
<br> |
||||
|
<h3>GCC IT Cell Outsourced Manpower Details</h3> |
||||
|
<br> |
||||
|
<h2>Login</h2> |
||||
|
<!--<form method="post" th:action="@{/liveurls/api/val-app-usr-login}" id="frm-usr-login-det" |
||||
|
name="frm-usr-login-det">--> |
||||
|
<form id="frm-usr-login-det" name="frm-usr-login-det"> |
||||
|
<div class="input-field"> |
||||
|
<input type="text" name="usrName" id="usrName" placeholder="Enter Username"> |
||||
|
<div class="underline"></div> |
||||
|
</div> |
||||
|
<div class="input-field"> |
||||
|
<input type="password" name="usrPass" id="usrPass" placeholder="Enter Password"> |
||||
|
<div class="underline"></div> |
||||
|
</div> |
||||
|
<input type="submit" value="Login" onclick="handleLoginSubmit(event)"> |
||||
|
</form> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Footer Fragment --> |
||||
|
<th:block th:replace="~{fragments/footer :: footer}"></th:block> |
||||
|
|
||||
|
<!-- Custom JS --> |
||||
|
<!--<script th:src="@{/assets/js/login-script.js}" type="text/javascript"></script>--> |
||||
|
<script th:inline="javascript"> |
||||
|
const contextPath = /*[[@{/}]]*/ ''; |
||||
|
</script> |
||||
|
|
||||
|
<script> |
||||
|
function handleLoginSubmit(event) { |
||||
|
event.preventDefault(); |
||||
|
|
||||
|
const form = document.getElementById("frm-usr-login-det"); |
||||
|
const formData = new FormData(form); |
||||
|
|
||||
|
fetch(contextPath + 'api/val-app-usr-login', { |
||||
|
method: 'POST', |
||||
|
body: formData |
||||
|
}) |
||||
|
.then(response => response.json()) |
||||
|
.then(data => { |
||||
|
if (data.result) { |
||||
|
let usrName = data.usrName && data.usrName.length > 0 ? data.usrName : null; |
||||
|
if (usrName) { |
||||
|
console.log("Redirecting to:", contextPath + 'redirect'); |
||||
|
alert("Login Success"); |
||||
|
window.location.href = contextPath + 'redirect'; |
||||
|
} else { |
||||
|
alert("No role found for this user!"); |
||||
|
} |
||||
|
} else { |
||||
|
alert(data.error || "Login failed"); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error during login:", err); |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,941 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html xmlns:th="http://www.thymeleaf.org" lang="en"> |
||||
|
|
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>GCC IT Cell Outsourced Manpower Details</title> |
||||
|
|
||||
|
<!-- CSRF Tokens --> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf" th:content="${_csrf.token}" /> |
||||
|
<meta th:if="${_csrf != null}" name="_csrf_header" th:content="${_csrf.headerName}" /> |
||||
|
|
||||
|
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/body-form-styles.css}"> |
||||
|
<link rel="stylesheet" type="text/css" th:href="@{/assets/css/home-styles.css}"> |
||||
|
|
||||
|
<th:block th:replace="~{fragments/header :: header}"></th:block> |
||||
|
|
||||
|
<style> |
||||
|
.spreadsheet-container { |
||||
|
background: #ffffff; |
||||
|
border-radius: 12px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
||||
|
padding: 25px; |
||||
|
margin: 20px auto; |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
.sheet-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20px; |
||||
|
border-bottom: 2px solid #eef2f5; |
||||
|
padding-bottom: 15px; |
||||
|
} |
||||
|
|
||||
|
.sheet-title { |
||||
|
color: #0f5c78; |
||||
|
font-weight: 800; |
||||
|
font-size: 1.8rem; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
} |
||||
|
|
||||
|
.sheet-date-badge { |
||||
|
background: #eef7fa; |
||||
|
color: #0f5c78; |
||||
|
font-weight: 700; |
||||
|
padding: 6px 14px; |
||||
|
border-radius: 20px; |
||||
|
font-size: 0.9rem; |
||||
|
border: 1px solid #d4e7ee; |
||||
|
} |
||||
|
|
||||
|
.table-responsive { |
||||
|
overflow-x: auto; |
||||
|
border-radius: 8px; |
||||
|
border: 1px solid #dcdfe3; |
||||
|
} |
||||
|
|
||||
|
.sheet-table { |
||||
|
width: 100%; |
||||
|
border-collapse: collapse; |
||||
|
font-size: 0.92rem; |
||||
|
text-align: left; |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.sheet-table th { |
||||
|
background-color: #0f5c78; |
||||
|
color: #ffffff; |
||||
|
font-weight: 700; |
||||
|
padding: 12px 10px; |
||||
|
border: 1px solid #0b4960; |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
text-transform: uppercase; |
||||
|
font-size: 0.85rem; |
||||
|
letter-spacing: 0.5px; |
||||
|
} |
||||
|
|
||||
|
.sheet-table td { |
||||
|
padding: 8px 10px; |
||||
|
border: 1px solid #e1e4e8; |
||||
|
vertical-align: middle; |
||||
|
color: #2c3e50; |
||||
|
} |
||||
|
|
||||
|
.sheet-table tbody tr:nth-child(even) { |
||||
|
background-color: #f9fbfb; |
||||
|
} |
||||
|
|
||||
|
.sheet-table tbody tr:hover { |
||||
|
background-color: #f1f6f8; |
||||
|
} |
||||
|
|
||||
|
.col-center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.col-right { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
/* Inputs */ |
||||
|
.sheet-input-number { |
||||
|
width: 75px; |
||||
|
padding: 5px 8px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
text-align: center; |
||||
|
font-weight: 600; |
||||
|
color: #0f5c78; |
||||
|
transition: border-color 0.2s, box-shadow 0.2s; |
||||
|
} |
||||
|
|
||||
|
.sheet-input-number:focus { |
||||
|
border-color: #1e82a6; |
||||
|
box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15); |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.sheet-input-text { |
||||
|
width: 90%; |
||||
|
min-width: 150px; |
||||
|
padding: 5px 10px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
font-weight: 500; |
||||
|
transition: border-color 0.2s, box-shadow 0.2s; |
||||
|
} |
||||
|
|
||||
|
.sheet-input-text:focus { |
||||
|
border-color: #1e82a6; |
||||
|
box-shadow: 0 0 0 3px rgba(30, 130, 166, 0.15); |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
/* Totals Row */ |
||||
|
.totals-row { |
||||
|
background-color: #e8eff2 !important; |
||||
|
font-weight: 800; |
||||
|
border-top: 2px solid #0f5c78; |
||||
|
} |
||||
|
|
||||
|
.totals-row td { |
||||
|
color: #0f5c78 !important; |
||||
|
font-size: 0.98rem; |
||||
|
border-top: 2px solid #0f5c78; |
||||
|
} |
||||
|
|
||||
|
/* Action Buttons */ |
||||
|
.action-bar { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
margin-top: 20px; |
||||
|
gap: 15px; |
||||
|
} |
||||
|
|
||||
|
.save-btn { |
||||
|
background-color: #10b981; |
||||
|
color: white; |
||||
|
border: none; |
||||
|
padding: 12px 28px; |
||||
|
font-size: 1rem; |
||||
|
font-weight: 700; |
||||
|
border-radius: 6px; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
box-shadow: 0 4px 6px rgba(16, 185, 129, 0.2); |
||||
|
transition: background-color 0.2s, transform 0.1s; |
||||
|
} |
||||
|
|
||||
|
.save-btn:hover { |
||||
|
background-color: #059669; |
||||
|
} |
||||
|
|
||||
|
.save-btn:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
/* Remarks styling */ |
||||
|
.vacant-highlight { |
||||
|
color: #e74c3c; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
.no-vacant { |
||||
|
color: #27ae60; |
||||
|
font-weight: 700; |
||||
|
} |
||||
|
|
||||
|
/* Custom Dashboard Tabs */ |
||||
|
.dashboard-tabs { |
||||
|
display: flex; |
||||
|
gap: 15px; |
||||
|
margin-bottom: 25px; |
||||
|
border-bottom: 2px solid #eef2f5; |
||||
|
padding-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.dashboard-tab { |
||||
|
padding: 10px 22px; |
||||
|
font-size: 1.05rem; |
||||
|
font-weight: 700; |
||||
|
color: #7f8c8d; |
||||
|
background: none; |
||||
|
border: none; |
||||
|
border-radius: 8px 8px 0 0; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.dashboard-tab:hover { |
||||
|
color: #0f5c78; |
||||
|
background-color: rgba(15, 92, 120, 0.05); |
||||
|
} |
||||
|
|
||||
|
.dashboard-tab.active { |
||||
|
color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
.dashboard-tab.active::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
bottom: -12px; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 4px; |
||||
|
background-color: #0f5c78; |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
|
||||
|
/* Vendor Directory Styles */ |
||||
|
.vendor-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); |
||||
|
gap: 20px; |
||||
|
margin-bottom: 30px; |
||||
|
} |
||||
|
|
||||
|
.vendor-card { |
||||
|
background: #ffffff; |
||||
|
border: 1px solid #eef2f6; |
||||
|
border-radius: 12px; |
||||
|
padding: 22px; |
||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02); |
||||
|
transition: all 0.3s ease; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: space-between; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.vendor-card::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 4px; |
||||
|
height: 100%; |
||||
|
background-color: #0f5c78; |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
|
||||
|
.vendor-card:hover { |
||||
|
transform: translateY(-5px); |
||||
|
box-shadow: 0 8px 25px rgba(15, 92, 120, 0.08); |
||||
|
border-color: #d4e7ee; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: start; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-title { |
||||
|
font-size: 1.15rem; |
||||
|
font-weight: 800; |
||||
|
color: #2c3e50; |
||||
|
margin: 0; |
||||
|
text-transform: capitalize; |
||||
|
line-height: 1.3; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-desc { |
||||
|
font-size: 0.9rem; |
||||
|
color: #7f8c8d; |
||||
|
line-height: 1.4; |
||||
|
margin-bottom: 20px; |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
.vendor-card-footer { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding-top: 15px; |
||||
|
border-top: 1px dashed #eef2f6; |
||||
|
} |
||||
|
|
||||
|
.vendor-badge { |
||||
|
font-size: 0.75rem; |
||||
|
font-weight: 700; |
||||
|
text-transform: uppercase; |
||||
|
padding: 4px 10px; |
||||
|
border-radius: 12px; |
||||
|
background-color: #eef7fa; |
||||
|
color: #0f5c78; |
||||
|
} |
||||
|
|
||||
|
/* Employee Count Interactive Badge */ |
||||
|
.emp-count-badge { |
||||
|
background: linear-gradient(135deg, #0f5c78, #1e82a6); |
||||
|
color: #ffffff; |
||||
|
font-weight: 800; |
||||
|
font-size: 1.05rem; |
||||
|
padding: 8px 18px; |
||||
|
border-radius: 30px; |
||||
|
cursor: pointer; |
||||
|
border: none; |
||||
|
box-shadow: 0 4px 10px rgba(30, 130, 166, 0.25); |
||||
|
transition: all 0.2s ease; |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.emp-count-badge:hover { |
||||
|
transform: scale(1.08); |
||||
|
box-shadow: 0 6px 15px rgba(30, 130, 166, 0.4); |
||||
|
background: linear-gradient(135deg, #1e82a6, #249cc8); |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
|
||||
|
.emp-count-badge:active { |
||||
|
transform: scale(0.96); |
||||
|
} |
||||
|
|
||||
|
/* Modal styling overrides */ |
||||
|
.modal-custom-header { |
||||
|
background-color: #0f5c78; |
||||
|
color: white; |
||||
|
border-top-left-radius: 14px; |
||||
|
border-top-right-radius: 14px; |
||||
|
padding: 20px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.modal-custom-title { |
||||
|
font-weight: 800; |
||||
|
font-size: 1.35rem; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box { |
||||
|
position: relative; |
||||
|
margin-bottom: 20px; |
||||
|
flex-grow: 1; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box input { |
||||
|
padding: 10px 15px 10px 40px; |
||||
|
border: 1px solid #dcdfe3; |
||||
|
border-radius: 8px; |
||||
|
width: 100%; |
||||
|
font-size: 0.95rem; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box input:focus { |
||||
|
border-color: #0f5c78; |
||||
|
box-shadow: 0 0 0 3px rgba(15, 92, 120, 0.15); |
||||
|
outline: none; |
||||
|
} |
||||
|
|
||||
|
.modal-search-box i { |
||||
|
position: absolute; |
||||
|
left: 15px; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
color: #7f8c8d; |
||||
|
} |
||||
|
|
||||
|
.modal-table { |
||||
|
font-size: 0.9rem; |
||||
|
} |
||||
|
|
||||
|
.modal-table th { |
||||
|
background-color: #eef7fa !important; |
||||
|
color: #0f5c78 !important; |
||||
|
font-weight: 700; |
||||
|
text-transform: uppercase; |
||||
|
font-size: 0.8rem; |
||||
|
letter-spacing: 0.5px; |
||||
|
border-bottom: 2px solid #0f5c78 !important; |
||||
|
} |
||||
|
|
||||
|
.modal-table td { |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.modal-body { |
||||
|
padding: 25px; |
||||
|
} |
||||
|
|
||||
|
.export-btn { |
||||
|
background-color: #27ae60; |
||||
|
color: white; |
||||
|
border: none; |
||||
|
padding: 8px 16px; |
||||
|
font-size: 0.9rem; |
||||
|
font-weight: 700; |
||||
|
border-radius: 6px; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
transition: all 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.export-btn:hover { |
||||
|
background-color: #219653; |
||||
|
color: white; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<th:block th:replace="~{fragments/navbar :: navbar(curPage=${curPage}, isAdmin=${isAdmin})}"></th:block> |
||||
|
|
||||
|
<div class="container" style="max-width: 95%;"> |
||||
|
<div class="spreadsheet-container"> |
||||
|
|
||||
|
<!-- Dashboard Tabs --> |
||||
|
<div class="dashboard-tabs"> |
||||
|
<button class="dashboard-tab active" id="btn-vendor-tab" onclick="switchTab('vendor-tab')"> |
||||
|
<i class="fa-solid fa-users-gear"></i> Vendor Directory & Counts |
||||
|
</button> |
||||
|
<button class="dashboard-tab" id="btn-manpower-tab" onclick="switchTab('manpower-tab')"> |
||||
|
<i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Tab 1: Vendor Directory & Counts --> |
||||
|
<div id="vendor-tab-content" class="tab-content"> |
||||
|
<div class="sheet-header"> |
||||
|
<h2 class="sheet-title"> |
||||
|
<i class="fa-solid fa-cubes text-primary"></i> Vendor Directory & Employees |
||||
|
</h2> |
||||
|
<span class="sheet-date-badge">Overall Count Wise Summary</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="vendor-grid"> |
||||
|
<div class="vendor-card" th:each="vendor : ${vendorDetails}"> |
||||
|
<div class="vendor-card-header"> |
||||
|
<h3 class="vendor-card-title" th:text="${vendor.vendor_name}">Vendor Name</h3> |
||||
|
<span class="vendor-badge" th:text="'Order: ' + ${vendor.orderby}">Order</span> |
||||
|
</div> |
||||
|
<p class="vendor-card-desc" |
||||
|
th:text="${vendor.description != null ? vendor.description : 'No description provided.'}"> |
||||
|
Description</p> |
||||
|
<div class="vendor-card-footer"> |
||||
|
<span style="font-weight: 600; color: #7f8c8d; font-size: 0.9rem;"> |
||||
|
Employees Count: |
||||
|
</span> |
||||
|
<button class="emp-count-badge" |
||||
|
th:attr="onclick=|fetchAndShowEmployees('${vendor.vendor_name}')|"> |
||||
|
<i class="fa-solid fa-users"></i> |
||||
|
<span th:text="${vendor.actual_employee_count}">0</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Tab 2: Manpower Statistics (Existing Spreadsheet) --> |
||||
|
<div id="manpower-tab-content" class="tab-content" style="display: none;"> |
||||
|
<div class="sheet-header"> |
||||
|
<h2 class="sheet-title"> |
||||
|
<i class="fa-solid fa-file-spreadsheet"></i> Manpower Statistics Report |
||||
|
</h2> |
||||
|
<span class="sheet-date-badge" id="current-date-badge">As on 21.05.2026</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="table-responsive"> |
||||
|
<table class="sheet-table" id="manpower-sheet-table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th style="width: 50px;">S.No</th> |
||||
|
<th>Description (Designation)</th> |
||||
|
<th>Vendor Name</th> |
||||
|
<th style="width: 120px;">Manpower Count<br>as per Tender</th> |
||||
|
<th style="width: 120px;">Available<br>Manpower Count</th> |
||||
|
<th>Working Location</th> |
||||
|
<th style="width: 110px;">Registered<br>in App</th> |
||||
|
<th>Vacant / Remarks</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr th:each="entry, stat : ${manpowerEntries}"> |
||||
|
<td class="col-center" th:text="${stat.count}">1</td> |
||||
|
<td th:text="${entry.description}" style="font-weight: 600;">Description</td> |
||||
|
<td th:text="${entry.vendor_name}">Vendor Name</td> |
||||
|
|
||||
|
<!-- Read-only Tender Count --> |
||||
|
<td class="col-center tender-cell" th:text="${entry.tender_count}" |
||||
|
style="font-weight: 700;">0</td> |
||||
|
|
||||
|
<!-- Editable Available Count --> |
||||
|
<td class="col-center"> |
||||
|
<input type="number" min="0" class="sheet-input-number available-input" |
||||
|
th:value="${entry.available_count}" th:data-id="${entry.id}" |
||||
|
oninput="recalculateTotals()"> |
||||
|
</td> |
||||
|
|
||||
|
<td th:text="${entry.working_location}">Location</td> |
||||
|
<td class="col-center" th:text="${entry.registered_count}">0</td> |
||||
|
|
||||
|
<!-- Editable Remarks --> |
||||
|
<td> |
||||
|
<input type="text" class="sheet-input-text remarks-input" |
||||
|
th:value="${entry.vacant_remarks}" th:data-id="${entry.id}" |
||||
|
oninput="recalculateTotals()"> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
<tfoot> |
||||
|
<tr class="totals-row"> |
||||
|
<td colspan="3" style="text-align: right; padding-right: 20px;">Total No</td> |
||||
|
<td class="col-center" id="total-tender">319</td> |
||||
|
<td class="col-center" id="total-available">292</td> |
||||
|
<td></td> |
||||
|
<td></td> |
||||
|
<td id="total-vacant" style="font-weight: 800; padding-left: 20px;">30</td> |
||||
|
</tr> |
||||
|
</tfoot> |
||||
|
</table> |
||||
|
</div> |
||||
|
|
||||
|
<div class="action-bar"> |
||||
|
<button class="save-btn" onclick="saveManpowerData()"> |
||||
|
<i class="fa-solid fa-floppy-disk"></i> Save Changes |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Employee Details Modal --> |
||||
|
<div class="modal fade" id="employeeDetailsModal" tabindex="-1" aria-labelledby="employeeDetailsModalLabel" |
||||
|
aria-hidden="true"> |
||||
|
<div class="modal-dialog modal-xl modal-dialog-centered"> |
||||
|
<div class="modal-content" |
||||
|
style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);"> |
||||
|
<div class="modal-custom-header"> |
||||
|
<h5 class="modal-custom-title" id="employeeDetailsModalLabel"> |
||||
|
<i class="fa-solid fa-users-viewfinder"></i> Employee Details - <span id="modal-vendor-name" |
||||
|
style="text-decoration: underline;">Vendor</span> |
||||
|
</h5> |
||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" |
||||
|
aria-label="Close"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<!-- Search and Export Toolbar --> |
||||
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center gap-3 mb-4"> |
||||
|
<div class="modal-search-box flex-grow-1 m-0"> |
||||
|
<i class="fa-solid fa-magnifying-glass"></i> |
||||
|
<input type="text" id="employee-search-input" |
||||
|
placeholder="Search employees by name, designation, location, code..." |
||||
|
oninput="filterEmployees()"> |
||||
|
</div> |
||||
|
<div class="d-flex align-items-center gap-2 w-100 w-md-auto justify-content-md-end"> |
||||
|
<select id="modal-designation-filter" class="form-select form-select-sm" |
||||
|
style="height: 44px; border-radius: 8px; border: 1px solid #cbd5e1; padding: 0 35px 0 15px; font-weight: 600; min-width: 200px;" |
||||
|
onchange="filterEmployees()"> |
||||
|
<option value="">- All Designations -</option> |
||||
|
</select> |
||||
|
<button class="export-btn text-nowrap" onclick="exportEmployeesToCSV()"> |
||||
|
<i class="fa-solid fa-file-csv"></i> Export to CSV |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Table of Employees --> |
||||
|
<div class="table-responsive" style="max-height: 450px; border: 1px solid #eef2f6;"> |
||||
|
<table class="table table-hover modal-table" id="modal-employee-table"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th style="width: 60px;" class="text-center">S.No</th> |
||||
|
<th>Emp Code</th> |
||||
|
<th>Name</th> |
||||
|
<th>Email</th> |
||||
|
<th>Mobile</th> |
||||
|
<th>User Type</th> |
||||
|
<th>Designation</th> |
||||
|
<th>Location</th> |
||||
|
<th>Vendor Name</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody id="modal-employee-tbody"> |
||||
|
<!-- Dynamically populated --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<div id="no-employees-found" class="text-center text-muted py-5" style="display: none;"> |
||||
|
<i class="fa-solid fa-user-slash fs-1 mb-3 text-secondary"></i> |
||||
|
<p class="fw-bold mb-0">No matching employees found for this vendor.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<th:block th:replace="~{fragments/footer :: footer}"></th:block> |
||||
|
|
||||
|
<!-- Inline Spreadsheet Script --> |
||||
|
<script th:inline="javascript"> |
||||
|
const contextPath = /*[[@{/}]]*/ ''; |
||||
|
const vendorDetailsData = /*[[${vendorDetails}]]*/[]; |
||||
|
const allDesignations = /*[[${designations}]]*/[]; |
||||
|
|
||||
|
// Globals to store current modal state |
||||
|
let currentVendorName = ""; |
||||
|
let currentVendorEmployees = []; |
||||
|
let filteredVendorEmployees = []; |
||||
|
|
||||
|
// Handle switching between dynamic dashboard and spreadsheet |
||||
|
function switchTab(tabId) { |
||||
|
const tabs = document.querySelectorAll(".dashboard-tab"); |
||||
|
const tabContents = document.querySelectorAll(".tab-content"); |
||||
|
|
||||
|
tabs.forEach(t => t.classList.remove("active")); |
||||
|
tabContents.forEach(c => c.style.display = "none"); |
||||
|
|
||||
|
if (tabId === 'vendor-tab') { |
||||
|
document.getElementById("btn-vendor-tab").classList.add("active"); |
||||
|
document.getElementById("vendor-tab-content").style.display = "block"; |
||||
|
} else if (tabId === 'manpower-tab') { |
||||
|
document.getElementById("btn-manpower-tab").classList.add("active"); |
||||
|
document.getElementById("manpower-tab-content").style.display = "block"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fetch employee details from API dynamically and render in Modal |
||||
|
function fetchAndShowEmployees(vendorName, designationFilter = null) { |
||||
|
currentVendorName = vendorName; |
||||
|
|
||||
|
let displayTitle = vendorName; |
||||
|
document.getElementById("modal-vendor-name").textContent = displayTitle; |
||||
|
|
||||
|
// Populate modal designation dropdown based on selected vendor ID |
||||
|
const modalDesigFilter = document.getElementById("modal-designation-filter"); |
||||
|
modalDesigFilter.innerHTML = '<option value="">- All Designations -</option>'; |
||||
|
|
||||
|
const vendor = vendorDetailsData.find(v => v.vendor_name === vendorName); |
||||
|
if (vendor && vendor.designationDetails) { |
||||
|
vendor.designationDetails.forEach(des => { |
||||
|
const opt = document.createElement("option"); |
||||
|
opt.value = des.designation; |
||||
|
|
||||
|
let displayText = des.designation; |
||||
|
const fileNo = des.File_no || des.file_no; |
||||
|
if (fileNo && fileNo !== '-' && fileNo !== '') { |
||||
|
displayText += " - " + fileNo; |
||||
|
} |
||||
|
opt.textContent = displayText; |
||||
|
modalDesigFilter.appendChild(opt); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (designationFilter) { |
||||
|
modalDesigFilter.value = designationFilter; |
||||
|
} else { |
||||
|
modalDesigFilter.value = ""; |
||||
|
} |
||||
|
|
||||
|
// Show Loading state inside tbody |
||||
|
const tbody = document.getElementById("modal-employee-tbody"); |
||||
|
tbody.innerHTML = ` |
||||
|
<tr> |
||||
|
<td colspan="9" class="text-center py-5"> |
||||
|
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"> |
||||
|
<span class="visually-hidden">Loading...</span> |
||||
|
</div> |
||||
|
<p class="mt-3 text-secondary fw-semibold">Fetching employee records from database...</p> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`; |
||||
|
document.getElementById("no-employees-found").style.display = "none"; |
||||
|
document.getElementById("employee-search-input").value = ""; |
||||
|
|
||||
|
// Show Modal using Bootstrap |
||||
|
const modalElement = document.getElementById("employeeDetailsModal"); |
||||
|
const modal = bootstrap.Modal.getOrCreateInstance(modalElement); |
||||
|
modal.show(); |
||||
|
|
||||
|
// Fetch from REST API |
||||
|
fetch(contextPath + 'api/vendor-employees?vendorName=' + encodeURIComponent(vendorName)) |
||||
|
.then(res => res.json()) |
||||
|
.then(data => { |
||||
|
currentVendorEmployees = data; |
||||
|
filterEmployees(); |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error fetching employees:", err); |
||||
|
tbody.innerHTML = ` |
||||
|
<tr> |
||||
|
<td colspan="9" class="text-center py-5 text-danger fw-bold"> |
||||
|
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i> |
||||
|
<p>Failed to connect to server and fetch database records. Please try again.</p> |
||||
|
</td> |
||||
|
</tr> |
||||
|
`; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Render the employee list inside the modal table |
||||
|
function renderEmployeeTable(employees) { |
||||
|
const tbody = document.getElementById("modal-employee-tbody"); |
||||
|
const noEmployeesEl = document.getElementById("no-employees-found"); |
||||
|
tbody.innerHTML = ""; |
||||
|
|
||||
|
if (employees.length === 0) { |
||||
|
noEmployeesEl.style.display = "block"; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
noEmployeesEl.style.display = "none"; |
||||
|
|
||||
|
employees.forEach((emp, index) => { |
||||
|
const tr = document.createElement("tr"); |
||||
|
tr.innerHTML = ` |
||||
|
<td class="text-center fw-bold text-secondary">${index + 1}</td> |
||||
|
<td class="fw-semibold text-primary">${escapeHtml(emp.emp_code || '-')}</td> |
||||
|
<td class="fw-bold text-dark">${escapeHtml(emp.name || '-')}</td> |
||||
|
<td>${escapeHtml(emp.email || '-')}</td> |
||||
|
<td>${escapeHtml(emp.mobile || '-')}</td> |
||||
|
<td><span class="badge bg-secondary">${escapeHtml(emp.user_type || '-')}</span></td> |
||||
|
<td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td> |
||||
|
<td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td> |
||||
|
<td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td> |
||||
|
`; |
||||
|
tbody.appendChild(tr); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Instant fuzzy client-side search inside the modal |
||||
|
function filterEmployees() { |
||||
|
const query = document.getElementById("employee-search-input").value.toLowerCase().trim(); |
||||
|
const selectedDesignation = document.getElementById("modal-designation-filter").value.toLowerCase().trim(); |
||||
|
|
||||
|
let list = currentVendorEmployees; |
||||
|
|
||||
|
// Apply designation filter |
||||
|
if (selectedDesignation !== "") { |
||||
|
list = list.filter(emp => emp.designation && emp.designation.trim().toLowerCase() === selectedDesignation); |
||||
|
} |
||||
|
|
||||
|
if (query === "") { |
||||
|
filteredVendorEmployees = list; |
||||
|
} else { |
||||
|
filteredVendorEmployees = list.filter(emp => { |
||||
|
return (emp.name && emp.name.toLowerCase().includes(query)) || |
||||
|
(emp.emp_code && emp.emp_code.toLowerCase().includes(query)) || |
||||
|
(emp.designation && emp.designation.toLowerCase().includes(query)) || |
||||
|
(emp.location && emp.location.toLowerCase().includes(query)) || |
||||
|
(emp.email && emp.email.toLowerCase().includes(query)) || |
||||
|
(emp.mobile && emp.mobile.toLowerCase().includes(query)) || |
||||
|
(emp.vendor_name && emp.vendor_name.toLowerCase().includes(query)) || |
||||
|
(emp.user_type && emp.user_type.toLowerCase().includes(query)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
renderEmployeeTable(filteredVendorEmployees); |
||||
|
} |
||||
|
|
||||
|
// Premium CSV Exporter |
||||
|
function exportEmployeesToCSV() { |
||||
|
if (filteredVendorEmployees.length === 0) { |
||||
|
alert("No employee records to export!"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let csvContent = "data:text/csv;charset=utf-8,"; |
||||
|
csvContent += "S.No,Employee Code,Name,Email,Mobile,User Type,Designation,Location,Vendor Name\n"; |
||||
|
|
||||
|
filteredVendorEmployees.forEach((emp, index) => { |
||||
|
const row = [ |
||||
|
index + 1, |
||||
|
`"${(emp.emp_code || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.name || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.email || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.mobile || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.user_type || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.designation || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.location || '-').replace(/"/g, '""')}"`, |
||||
|
`"${(emp.vendor_name || '-').replace(/"/g, '""')}"` |
||||
|
]; |
||||
|
csvContent += row.join(",") + "\n"; |
||||
|
}); |
||||
|
|
||||
|
const encodedUri = encodeURI(csvContent); |
||||
|
const link = document.createElement("a"); |
||||
|
link.setAttribute("href", encodedUri); |
||||
|
|
||||
|
// Filename format: [vendor_name]_employees.csv |
||||
|
const sanitizedVendorName = currentVendorName.toLowerCase().replace(/[^a-z0-9]/g, "_"); |
||||
|
link.setAttribute("download", `${sanitizedVendorName}_employees.csv`); |
||||
|
document.body.appendChild(link); |
||||
|
link.click(); |
||||
|
document.body.removeChild(link); |
||||
|
} |
||||
|
|
||||
|
// Helper function to safely escape HTML to prevent XSS |
||||
|
function escapeHtml(str) { |
||||
|
if (!str) return ''; |
||||
|
return String(str) |
||||
|
.replace(/&/g, "&") |
||||
|
.replace(/</g, "<") |
||||
|
.replace(/>/g, ">") |
||||
|
.replace(/"/g, """) |
||||
|
.replace(/'/g, "'"); |
||||
|
} |
||||
|
|
||||
|
// Dynamic calculation when page loads |
||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||
|
// Update the date badge dynamically to current date format |
||||
|
const d = new Date(); |
||||
|
const day = String(d.getDate()).padStart(2, '0'); |
||||
|
const month = String(d.getMonth() + 1).padStart(2, '0'); |
||||
|
const year = d.getFullYear(); |
||||
|
document.getElementById("current-date-badge").textContent = `As on ${day}.${month}.${year}`; |
||||
|
|
||||
|
recalculateTotals(); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
// Recalculates Tender, Available, and Vacant counts dynamically in the footer |
||||
|
function recalculateTotals() { |
||||
|
const table = document.getElementById("manpower-sheet-table"); |
||||
|
const rows = table.querySelectorAll("tbody tr"); |
||||
|
|
||||
|
let tenderSum = 0; |
||||
|
let availableSum = 0; |
||||
|
let vacantSum = 0; |
||||
|
|
||||
|
rows.forEach(row => { |
||||
|
// Tender sum |
||||
|
const tenderVal = parseInt(row.querySelector(".tender-cell").textContent) || 0; |
||||
|
tenderSum += tenderVal; |
||||
|
|
||||
|
// Available sum |
||||
|
const availableInput = row.querySelector(".available-input"); |
||||
|
const availableVal = parseInt(availableInput.value) || 0; |
||||
|
availableSum += availableVal; |
||||
|
|
||||
|
// Vacant Sum parsing: Extract leading digit or number from vacant input |
||||
|
const remarksInput = row.querySelector(".remarks-input"); |
||||
|
const remarksVal = remarksInput.value.trim(); |
||||
|
|
||||
|
// Extract the first number found in the remarks input |
||||
|
const numMatch = remarksVal.match(/^-?\d+/); |
||||
|
if (numMatch) { |
||||
|
vacantSum += parseInt(numMatch[0]); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
document.getElementById("total-tender").textContent = tenderSum; |
||||
|
document.getElementById("total-available").textContent = availableSum; |
||||
|
document.getElementById("total-vacant").textContent = vacantSum; |
||||
|
} |
||||
|
|
||||
|
// Save dynamic spreadsheet updates back to database |
||||
|
function saveManpowerData() { |
||||
|
const table = document.getElementById("manpower-sheet-table"); |
||||
|
const rows = table.querySelectorAll("tbody tr"); |
||||
|
const payload = []; |
||||
|
|
||||
|
rows.forEach(row => { |
||||
|
const availableInput = row.querySelector(".available-input"); |
||||
|
const remarksInput = row.querySelector(".remarks-input"); |
||||
|
|
||||
|
payload.push({ |
||||
|
id: parseInt(availableInput.dataset.id), |
||||
|
availableCount: parseInt(availableInput.value) || 0, |
||||
|
vacantRemarks: remarksInput.value |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
// CSRF tokens |
||||
|
const csrfToken = document.querySelector("meta[name='_csrf']")?.getAttribute("content"); |
||||
|
const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content"); |
||||
|
|
||||
|
const headers = { |
||||
|
'Content-Type': 'application/json' |
||||
|
}; |
||||
|
|
||||
|
if (csrfToken && csrfHeader) { |
||||
|
headers[csrfHeader] = csrfToken; |
||||
|
} |
||||
|
|
||||
|
// Save to API |
||||
|
fetch(contextPath + 'api/save-manpower-stats', { |
||||
|
method: 'POST', |
||||
|
headers: headers, |
||||
|
body: JSON.stringify(payload) |
||||
|
}) |
||||
|
.then(res => res.json()) |
||||
|
.then(data => { |
||||
|
if (data.success) { |
||||
|
alert("Changes saved successfully to MySQL database!"); |
||||
|
window.location.reload(); |
||||
|
} else { |
||||
|
alert("Failed to save changes: " + (data.error || "Unknown error")); |
||||
|
} |
||||
|
}) |
||||
|
.catch(err => { |
||||
|
console.error("Error saving data:", err); |
||||
|
alert("Something went wrong. Failed to communicate with server."); |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<div th:fragment="login-modal"> |
||||
|
<!-- Overlay --> |
||||
|
<div id="login-modal" class="login-modal-overlay"> |
||||
|
<!-- Modal Box --> |
||||
|
<div class="login-modal-box"> |
||||
|
<span class="close-btn" onclick="closeLoginModal()">×</span> |
||||
|
<h3 class="login-title">Login</h3> |
||||
|
<form id="otp-form" class="login-form"> |
||||
|
<label for="mobNum" class="form-label">Mobile Number <span class="req">*</span></label> |
||||
|
<select id="mobNum" name="mobNum" class="form-input" required> |
||||
|
<option value="">-- Select Mobile Number --</option> |
||||
|
</select> |
||||
|
<!--<input type="tel" id="mobNum" name="mobNum" placeholder="Enter your mobile number" required |
||||
|
class="form-input">--> |
||||
|
<!-- Hidden OTP Section (initially hidden) --> |
||||
|
<div id="otp-section" style="display: none; margin-top: 15px;"> |
||||
|
<label for="otp" class="form-label">Enter OTP <span class="req">*</span></label> |
||||
|
<input type="number" id="otp" name="otp" placeholder="Enter received OTP" class="form-input" |
||||
|
maxlength="5"> |
||||
|
</div> |
||||
|
<!-- Button Row --> |
||||
|
<div class="btn-row" id="button-section"> |
||||
|
<button type="submit" id="send-otp-btn" class="login-btn-send">Get OTP</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
<!-- Message --> |
||||
|
<p id="otp-message" style="text-align:center; margin-top:10px; color:#333;"></p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue