Tower: upload tk_construction_management 18.0.2.0.8 (was 18.0.2.0.8, via marketplace)

This commit is contained in:
2026-05-08 19:20:12 +00:00
parent c412640ca2
commit 63c62699f5
174 changed files with 44759 additions and 0 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
.content-area {
max-width: 1070px;
margin: 0 auto;
}
#topnav {
background: #37474f;
height: 60px;
display: flex;
flex-direction: row;
align-items: center;
font-size: 14px;
}
.admin-menu {
color: #fff;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
flex: 0.05 0 0;
}
.logo {
display: flex;
flex-direction: row;
align-items: center;
flex: 1 0 0;
}
.logo-t {
width: 32px;
height: 32px;
border: 2px solid #26c6da;
text-align: center;
line-height: 28px;
border-radius: 50%;
margin-right: 15px;
margin-left: 5px;
padding-left: 3px;
}
.search-bar {
flex: 2 0 0;
align-items: center;
justify-content: space-between;
background: #232e34;
overflow: hidden;
display: flex;
height: 36px;
border-radius: 35px;
color: rgba(255, 255, 255, 0.5);
}
.search-bar-dropdown {
flex: 1 0 0;
height: 40px;
line-height: 40px;
padding: 0 18px;
margin-right: 15px;
background: #2c393f;
}
.search-bar-input {
flex: 2 0 0;
display: flex;
justify-content: flex-end;
padding: 0 18px;
line-height: 40px;
align-items: center;
}
.search-bar-input input[type="text"] {
width: 100%;
background: transparent;
border: 0;
color: rgba(255, 255, 255, 0.5);
}
.search-bar-input input:focus {
outline: none;
}
.box.banana_map {
color: #fff;
background: #eff4f7;
padding: 0;
box-shadow: none;
}
.box.banana_map .title {
padding-top: 40px;
padding-left: 25px;
font-size: 16px;
}
.box.banana_map .subtitle {
font-weight: 700;
padding-top: 10px;
padding-left: 25px;
font-size: 22px;
}
.box {
max-height: 444px;
}
.box-full {
max-height: 470px;
}
.box .banana {
min-height: 404px;
background-image: url('img/banana.png');
background-size: cover;
}
.box .map {
min-height: 404px;
background-image: url('img/map.png');
background-size: cover;
}
.box .cog-icon {
cursor: pointer;
position: absolute;
right: 55px;
top: 25px;
z-index: 10;
}
@media screen and (max-width:760px) {
#topnav {
flex-wrap: wrap;
}
.admin-menu {
flex-basis: 20%;
}
.logo {
justify-content: flex-end;
padding-right: 10px;
}
.logo {
flex-basis: 80%;
}
.topnav-rightmenu,
.search-bar {
display: none;
}
}
.box {
padding: 25px 35px 25px 30px;
height: 100vh;
background: #fff;
border-radius: 15px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 1);
}
#monthly-earnings-chart #apexcharts-canvas {
position: relative;
}
#monthly-earnings-chart #apexcharts-canvas:after {
content: "";
position: absolute;
left: 0;
right: 58%;
top: 0;
bottom: 0;
background: #24bdd3;
opacity: 0.65;
}
#monthly-earnings-chart #apexcharts-title-text {
font-weight: 600;
}
#monthly-earnings-chart #apexcharts-subtitle-text {
font-weight: 700;
}
.monthly-earnings-text {
position: absolute;
left: 70px;
top: 187px;
color: #fff;
z-index: 10;
}
.monthly-earnings-text h6 {
font-size: 16px;
}
.chart-title h5 {
font-size: 18px;
color: rgba(51, 51, 51, 1);
margin-bottom: 38px;
}
@media screen and (max-width:767px) {
.monthly-earnings-text {
left: 30px;
}
.box {
padding: 25px 2rem;
}
.pos_dashboard select#date_filter {
background-position: 29rem !important;
}
}
.sparkboxes .box {
padding: 3px 0 0 0;
position: relative;
}
#spark1,
#spark2,
#spark3,
#spark4 {
position: relative;
padding-top: 15px;
}
/* overrides */
.sparkboxes #apexcharts-subtitle-text {
fill: #8799a2 !important;
}
.height-auto {
height: auto !important;
max-height: none !important;
}
.spinner-border {
display: none;
}

View File

@@ -0,0 +1,212 @@
.content-area {
max-width: 1070px;
margin: 0 auto;
}
#topnav {
background: #37474f;
height: 60px;
display: flex;
flex-direction: row;
align-items: center;
font-size: 14px;
}
.admin-menu {
color: #fff;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
flex: 0.05 0 0;
}
.logo {
display: flex;
flex-direction: row;
align-items: center;
flex: 1 0 0;
}
.logo-t {
width: 32px;
height: 32px;
border: 2px solid #26c6da;
text-align: center;
line-height: 28px;
border-radius: 50%;
margin-right: 15px;
margin-left: 5px;
padding-left: 3px;
}
.search-bar {
flex: 2 0 0;
align-items: center;
justify-content: space-between;
background: #232e34;
overflow: hidden;
display: flex;
height: 36px;
border-radius: 35px;
color: rgba(255, 255, 255, 0.5);
}
.search-bar-dropdown {
flex: 1 0 0;
height: 40px;
line-height: 40px;
padding: 0 18px;
margin-right: 15px;
background: #2c393f;
}
.search-bar-input {
flex: 2 0 0;
display: flex;
justify-content: flex-end;
padding: 0 18px;
line-height: 40px;
align-items: center;
}
.search-bar-input input[type="text"] {
width: 100%;
background: transparent;
border: 0;
color: rgba(255, 255, 255, 0.5);
}
.search-bar-input input:focus {
outline: none;
}
.box {
max-height: 444px;
}
.box-full {
max-height: 470px;
}
.box .cog-icon {
cursor: pointer;
position: absolute;
right: 55px;
top: 25px;
z-index: 10;
}
@media screen and (max-width:760px) {
#topnav {
flex-wrap: wrap;
}
.admin-menu {
flex-basis: 20%;
}
.logo {
justify-content: flex-end;
padding-right: 10px;
}
.logo {
flex-basis: 80%;
}
.topnav-rightmenu,
.search-bar {
display: none;
}
}
.box {
padding: 25px 35px 25px 30px;
height: 100vh;
background: #fff;
border-radius: 15px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 1);
}
#monthly-earnings-chart #apexcharts-canvas {
position: relative;
}
#monthly-earnings-chart #apexcharts-canvas:after {
content: "";
position: absolute;
left: 0;
right: 58%;
top: 0;
bottom: 0;
background: #24bdd3;
opacity: 0.65;
}
#monthly-earnings-chart #apexcharts-title-text {
font-weight: 600;
}
#monthly-earnings-chart #apexcharts-subtitle-text {
font-weight: 700;
}
.monthly-earnings-text {
position: absolute;
left: 70px;
top: 187px;
color: #fff;
z-index: 10;
}
.monthly-earnings-text h6 {
font-size: 16px;
}
.chart-title h5 {
font-size: 18px;
color: rgba(51, 51, 51, 1);
margin-bottom: 38px;
}
@media screen and (max-width:760px) {
.monthly-earnings-text {
left: 30px;
}
.box {
padding: 25px 0;
}
}
.sparkboxes .box {
padding: 3px 0 0 0;
position: relative;
}
#spark1,
#spark2,
#spark3,
#spark4 {
position: relative;
padding-top: 15px;
}
/* overrides */
.sparkboxes #apexcharts-subtitle-text {
fill: #8799a2 !important;
}
.height-auto {
height: auto !important;
max-height: none !important;
}
.spinner-border {
display: none;
}

View File

@@ -0,0 +1,242 @@
.o_action_manager {
overflow: auto !important;
}
.construction_dashboard {
background-color: #f8f9fa !important;
height: 100%;
.d-flex {
display: flex;
}
.just-end {
justify-content: flex-end;
}
.tk-form-control {
width: 150px;
border-radius: 1px !important;
border: none !important;
background-color: #eef3f7;
border-color: #F5F8FA;
color: #5E6278;
transition: color 0.2s ease, background-color 0.2s ease;
}
.tk-btn-prime {
border-radius: 1px !important;
}
.me-2 {
margin-right: .5rem !important;
}
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: .475rem;
border: 0;
box-shadow: 0 0 20px 0 rgba(76, 87, 125, .02);
}
.card.card-xl-stretch.mb-xl-8:hover {
transform: translateY(-5px) scale(1.005) translateZ(0);
box-shadow: 0 24px 36px rgba(0, 0, 0, 0.11), 0 24px 46px transparent;
cursor: pointer;
}
.card-body {
flex: 1 1 auto;
padding: 1rem 1rem;
}
.card-p {
padding: 2rem 2.25rem !important;
}
.flex-stack {
justify-content: space-between;
align-items: center;
}
.flex-grow-1 {
flex-grow: 1 !important;
}
.d-flex {
display: flex !important;
}
.symbol {
display: inline-block;
flex-shrink: 0;
position: relative;
border-radius: .475rem;
}
.symbol .symbol-label {
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
color: #3f4254;
background-color: #f5f8fa;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
border-radius: .475rem;
width: 50px;
height: 50px;
}
.svg-icon.svg-icon-info svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #009ef7;
}
.svg-icon.svg-icon-warning svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #ffc700;
}
.svg-icon.svg-icon-success svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #50cd89;
}
.svg-icon.svg-icon-danger svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #f1416c;
}
.svg-icon.svg-icon-general svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #7239ea;
}
.svg-icon.svg-icon-progress svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #ff9800;
}
.bg-light-info {
background-color: #f1faff !important;
}
.bg-light-success {
background-color: #e8fff3 !important;
}
.bg-light-warning {
background-color: #fff8dd !important;
}
.bg-light-danger {
background-color: #fff5f8 !important;
}
.bg-light-progress {
background-color: #fff0ce !important;
}
.bg-light-ship {
background-color: #f8f5ff!important;
}
.text-end {
text-align: right !important;
}
.text-dark {
color: #181c32 !important;
}
.fw-bolder {
font-weight: 600 !important;
}
.text-muted {
color: #a1a5b7 !important;
font-weight: 700;
}
.btn.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
}
.btn-sm, .btn-group-sm > .btn {
padding: 0.25rem 0.5rem;
font-size: 0.94791667rem;
line-height: 1.5;
border-radius: 0.2rem;
background-color: #f5f8fa;
}
.svg-icon.svg-icon-3 svg {
height: 2rem !important;
width: 2rem !important;
padding: 0.35rem;
}
.svg-icon svg [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #a1a5b7;
&:hover{
fill: #009ef7;
}
}
.svg-icon svg:hover [fill]:not(.permanent):not(g) {
transition: fill .3s ease;
fill: #009ef7;
}
.badge {
display: inline-block;
padding: 0.5em 0.85em;
font-size: .85rem;
font-weight: 600;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.475rem;
border: none !important;
}
.badge-light-success {
color: #50cd89;
background-color: #e8fff3;
}
.badge-light-danger {
color: #f1416c;
background-color: #fff5f8;
}
.badge-light-info {
color: #009ef7;
background-color: #f1faff;
}
.badge-light-warning {
color: #ffc700;
background-color: #fff8dd;
}
.badge-light-primary {
color: #7239ea;
background-color: #f8f5ff;
}
.fw-bolder-7 {
font-weight: 700!important;
}
div.dataTables_wrapper div.dataTables_filter input:focus-visible{
outline: none !important;
}
div.dataTables_wrapper div.dataTables_filter input{
border: 1px solid gray !important;
}
.paginate_button{
margin-right: 10px !important;
}
}

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 100.89" style="enable-background:new 0 0 122.88 100.89" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g><path class="st0" d="M115.39,56.45l-0.07,2.6L90.3,66.32l-1.46-3.2c-0.81-1.66-4.09-0.71-3.37,1.76c1.04,3.54,2.64,6.28,4.16,9.07 c8.37,15.42,37.85,10.55,29.51-13.36l-0.07-4.54c2.24-0.97,3.81-3.2,3.81-5.8c0-3.49-2.83-6.32-6.32-6.32c-0.08,0-0.16,0-0.24,0 l2.73-1.16l-12.2-31.65C103.06-0.66,99-2.21,92.42,2.41L57.85,35.36l-0.84-0.84h-7.55c-2.3,0-4.18,1.88-4.18,4.18v15.97H33.74V53.9 c0-1.5-1.23-2.73-2.73-2.73H15.74c-1.5,0-2.73,1.23-2.73,2.73v0.77H8.79c-2.86,0-5.2,2.34-5.2,5.2v6.49c0,2.87,2.35,5.21,5.21,5.21 h55.25c2.87,0,5.21-2.35,5.21-5.21v-8.22v-3.47v-7.88l-1.84-1.85L95.39,15.4l14.18,31.39l2.62-1.11c-1.2,1.15-1.94,2.77-1.94,4.56 C110.24,53.33,112.46,55.9,115.39,56.45L115.39,56.45z M13.59,73.72h45.48c7.47,0,13.59,6.11,13.59,13.59l0,0 c0,7.47-6.11,13.59-13.59,13.59H13.59C6.11,100.89,0,94.78,0,87.3l0,0C0,79.83,6.11,73.72,13.59,73.72L13.59,73.72z M17.45,86.08 c0.16,0,0.28,0.55,0.28,1.23s-0.13,1.23-0.28,1.23h-1.09v1.09c0,0.16-0.55,0.28-1.23,0.28c-0.68,0-1.23-0.13-1.23-0.28v-1.09h-1.09 c-0.16,0-0.28-0.55-0.28-1.23s0.13-1.23,0.28-1.23h1.09v-1.09c0-0.16,0.55-0.28,1.23-0.28c0.68,0,1.23,0.13,1.23,0.28v1.09H17.45 L17.45,86.08z M56.42,84.98c0-0.16,0.55-0.28,1.23-0.28s1.23,0.13,1.23,0.28v1.09h1.09c0.16,0,0.28,0.55,0.28,1.23 s-0.13,1.23-0.28,1.23h-1.09v1.09c0,0.16-0.55,0.28-1.23,0.28s-1.23-0.13-1.23-0.28v-1.09h-1.09c-0.16,0-0.28-0.55-0.28-1.23 s0.13-1.23,0.28-1.23h1.09V84.98L56.42,84.98z M57.65,77.36c5.49,0,9.94,4.45,9.94,9.94s-4.45,9.94-9.94,9.94 c-5.49,0-9.94-4.45-9.94-9.94S52.16,77.36,57.65,77.36L57.65,77.36z M15.13,77.36c5.49,0,9.94,4.45,9.94,9.94s-4.45,9.94-9.94,9.94 c-5.49,0-9.94-4.45-9.94-9.94S9.64,77.36,15.13,77.36L15.13,77.36z M97.01,5.3c1.93,0,3.5,1.57,3.5,3.5c0,1.93-1.57,3.5-3.5,3.5 c-1.93,0-3.5-1.57-3.5-3.5C93.51,6.87,95.07,5.3,97.01,5.3L97.01,5.3z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 83.03" style="enable-background:new 0 0 122.88 83.03" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g><path class="st0" d="M0,83.03h23.34V54.25c0-0.19,0.03-0.38,0.08-0.56C4,56.45,4.96,65.41,0.03,83.01L0,83.03L0,83.03z M65.53,83.03h0.57h14.4v-0.03h2.92v0.01l0,0h21.54V64.55c0-0.22,0.05-0.45,0.15-0.64L94.2,63.99l-10.44-0.07 c-2.74-5.46-7.27-8.75-17.73-10.24c0.05,0.19,0.08,0.38,0.08,0.56v26.71c-0.17,0.67-0.36,1.35-0.56,2.06L65.53,83.03L65.53,83.03z M84.51,45.71v2.32l0.79,1.08c0.12,0.17,0.19,0.36,0.19,0.55c0.29,4.04,1.36,6.56,2.98,8.12c1.57,1.53,3.72,2.24,6.16,2.63 c2.65-0.43,4.71-1.55,6.19-3.27c1.53-1.81,2.46-4.32,2.75-7.5c0.02-0.15,0.07-0.31,0.14-0.45l0.71-1.17l0.02-2.41h2.08l-0.02,2.63 c0.02,0.21-0.03,0.41-0.15,0.62l-0.74,1.2c-0.36,3.49-1.45,6.31-3.22,8.41c-1.84,2.15-4.37,3.51-7.59,4.01 c-0.1,0.02-0.22,0.02-0.34,0c-2.89-0.45-5.49-1.29-7.47-3.23c-1.93-1.89-3.24-4.75-3.58-9.19l-0.76-1.05 c-0.17-0.19-0.26-0.43-0.26-0.69V45.7h2.1L84.51,45.71L84.51,45.71z M94.08,29.78c1,0,1.98,0.1,2.91,0.29l-0.17,8.38L99.86,31 c4.28,1.91,7.47,5.88,8.26,10.65h1.65v1.96h-1.46v0.03H79.83v-0.03h-1.19v-1.96h1.38c0.83-4.92,4.18-9,8.69-10.82l2.67,7.5 L91.65,30c0.79-0.14,1.58-0.21,2.41-0.21L94.08,29.78L94.08,29.78z M92.61,76.32h3.18v3.18h-3.18V76.32L92.61,76.32z M92.61,68.46 h3.18v3.18h-3.18V68.46L92.61,68.46z M107.91,83.02h14.97c-3.18-11.29-2.55-17.03-15-18.81c0.03,0.12,0.05,0.24,0.05,0.36v18.46 L107.91,83.02L107.91,83.02z M29.59,24.84v3.62l1.23,1.69c0.19,0.27,0.3,0.56,0.3,0.86c0.46,6.3,2.12,10.22,4.64,12.66 c2.44,2.39,5.79,3.49,9.6,4.1c4.13-0.67,7.35-2.41,9.66-5.1c2.39-2.82,3.84-6.73,4.29-11.7c0.03-0.24,0.11-0.48,0.21-0.7l1.1-1.82 l0.03-3.76h3.25l-0.03,4.1c0.03,0.32-0.05,0.64-0.24,0.97l-1.15,1.88c-0.56,5.45-2.25,9.85-5.02,13.12 c-2.87,3.35-6.81,5.47-11.83,6.25c-0.16,0.03-0.35,0.03-0.54,0c-4.51-0.7-8.56-2.01-11.64-5.04c-3-2.95-5.04-7.4-5.58-14.33 l-1.18-1.64c-0.27-0.3-0.4-0.67-0.4-1.07v-4.13h3.27L29.59,24.84L29.59,24.84z M44.51,0c1.56,0,3.09,0.16,4.53,0.46l-0.27,13.07 L53.52,1.9c6.68,2.98,11.64,9.18,12.88,16.61h2.58v3.06h-2.28v0.05h-44.4v-0.05h-1.85v-3.06h2.15c1.29-7.67,6.52-14.03,13.55-16.87 l4.16,11.7l0.43-12.98c1.23-0.21,2.47-0.32,3.76-0.32L44.51,0L44.51,0z M42.23,72.57h4.96v4.96h-4.96V72.57L42.23,72.57z M42.23,60.31h4.96v4.96h-4.96V60.31L42.23,60.31z M27.9,83.01h33.59V54.22c0-0.35,0.08-0.7,0.24-0.99L44.7,53.33l-17.04-0.11 c0.16,0.3,0.24,0.64,0.24,0.99V83.01L27.9,83.01L27.9,83.01z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 109.77"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>toolbox-repairing</title><path class="cls-1" d="M26.13,8.81A17.18,17.18,0,0,0,13.24,4.72c14,11.42-2.58,26.17-11.79,9-3.78,8.49.16,16.31,8,20.33,3.7,1.9,5.65,3,7,4.53a11.73,11.73,0,0,1,1.86,2.92l3.49,8.26H7.11A2.32,2.32,0,0,0,4.8,52.1V93a16.87,16.87,0,0,0,16.82,16.82H96.79A16.87,16.87,0,0,0,113.61,93V52.1a2.32,2.32,0,0,0-2.32-2.32H88.89l13.07-22-9.37-5.4L76.21,49.78H65.79l0-2.86a.57.57,0,0,0-.12-.35c-.5-.6-.88-1-1.2-1.41h0c-.74-.86-1.12-1.29-1.12-1.57s.36-.75,1.09-1.65c.3-.37.66-.81,1.08-1.36a.57.57,0,0,0,.13-.36V37.05a.55.55,0,0,0-.55-.55l-4.85,0,.09-22.09,2.92-5.11a2.85,2.85,0,0,0,.46-1.55l0-5A2.83,2.83,0,0,0,60.82,0L54,0a2.83,2.83,0,0,0-2.79,2.87l.08,5.54A2.79,2.79,0,0,0,51.81,10l2.73,3.91-.09,22.63-5.74,0a.55.55,0,0,0-.55.56v3.1a.54.54,0,0,0,.14.36c.47.52.87,1,1.22,1.32,1,1.07,1.48,1.59,1.49,2s-.41.86-1.19,1.85c-.39.49-.87,1.1-1.44,1.86a.5.5,0,0,0-.12.35v1.86H35.19l-5.5-12.91a12.84,12.84,0,0,1-1.11-3.79,14.19,14.19,0,0,1,.28-3.75c1.33-7.78,4-14.25-2.73-20.52ZM9.43,63.94H109V54.41H9.43v9.53ZM109,68.57H95.13v7.79A3.17,3.17,0,0,1,92,79.53H80.86a3.18,3.18,0,0,1-3.17-3.17V68.57H41.82v7.79a3.18,3.18,0,0,1-3.17,3.17H27.55a3.18,3.18,0,0,1-3.18-3.17V68.57H9.43V93a12.24,12.24,0,0,0,12.19,12.2H96.79A12.24,12.24,0,0,0,109,93V68.57ZM75.29,18.83a15.81,15.81,0,0,1,5.3-2.57,10.13,10.13,0,0,1,7.87.85l20.38,11.75L107.61,31l7.75,4.51,7.52-13L115.14,18l-1.4,2.36L93.48,8.68a10.31,10.31,0,0,0-8-1.38c-4.24,1.14-7.65,5-10.23,11.53Z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,255 @@
/** @odoo-module **/
import { Component, useRef, onMounted, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
import { _t } from "@web/core/l10n/translation";
export class ConstructionDashboard extends Component {
// static template = "ConstructionDashboard";
setup() {
this.rpc = rpc;
this.action = useService("action");
this.orm = useService("orm");
// reactive state
this.state = useState({
selectedSite: "all_site",
selectedProject: "all_project",
sites: [],
projects: [],
stats: {},
});
// Chart containers
this.refs = {
siteStateChart: useRef("siteStateChart"),
mrqStateChart: useRef("mrqStateChart"),
internalTransferChart: useRef("internalTransferChart"),
constructionTimeline: useRef("constructionTimeline"),
projectTimeline: useRef("projectTimeline"),
projectStatusChart: useRef("projectStatusChart"),
jobOrderPoChart: useRef("jobOrderPoChart"),
};
// onMounted(() => this.loadInitialData());
onMounted(() => {
this.loadInitialData();
});
}
async loadInitialData() {
// const result = await this.rpc({
// model: "tk.construction.dashboard",
// method: "get_construction_state",
// args: [false, false],
// });
const result = await this.orm.call("tk.construction.dashboard", "get_construction_state", [false, false]);
console.log(result,'dasdsa');
this.state.stats = result;
this.state.sites = Object.entries(result.con_sites || {}).map(([id, name]) => ({ id, name }));
this.renderCharts(result);
}
async onSiteChange(ev) {
this.state.selectedSite = ev.target.value;
this.state.selectedProject = "all_project";
const data = await this.orm.call("tk.construction.dashboard", "get_project_list", [this.state.selectedSite]);
// const data = await this.rpc({
// model: "tk.construction.dashboard",
// method: "get_project_list",
// args: [this.state.selectedSite],
// });
this.state.projects = Object.entries(data || {}).map(([id, name]) => ({ id, name }));
await this.updateStats();
}
async onProjectChange(ev) {
this.state.selectedProject = ev.target.value;
await this.updateStats();
}
async updateStats() {
// const data = await this.rpc({
// model: "tk.construction.dashboard",
// method: "get_construction_state",
// args: [this.state.selectedSite, this.state.selectedProject],
// });
const data = await this.orm.call("tk.construction.dashboard", "get_construction_state", [this.state.selectedSite, this.state.selectedProject]);
this.state.stats = data;
this.renderCharts(data);
console.log(this.state.stats,'dasdsa');
}
openAction(name, resModel, domain = []) {
this.action.doAction({
name: _t(name),
type: "ir.actions.act_window",
res_model: resModel,
domain,
views: [[false, "list"], [false, "form"]],
target: "current",
});
}
// 👇 Public methods that are bound in template
viewTotalSite = () => this.openAction("Project", "tk.construction.site");
async viewTotalProject() {
const domain = await this.getProjectDomain(0);
this.openAction("Projects", "tk.construction.project", domain);
}
async viewTotalMrq() {
const domain = await this.getProjectDomain(1);
this.openAction("Material Requisition", "material.requisition", domain);
}
async viewTotalJobSheet() {
const domain = await this.getProjectDomain(1);
this.openAction("Job Costing", "job.costing", domain);
}
async viewTotalJobOrder() {
const domain = await this.getProjectDomain(1);
this.openAction("Work Order", "job.order", domain);
}
async viewProjectPlanning() {
const domain = await this.getProjectDomain(0);
domain.push(["stage", "=", "Planning"]);
this.openAction("Project Planning", "tk.construction.project", domain);
}
async viewProjectProcurement() {
const domain = await this.getProjectDomain(0);
domain.push(["stage", "=", "Procurement"]);
this.openAction("Project Procurement", "tk.construction.project", domain);
}
async viewProjectConstruction() {
const domain = await this.getProjectDomain(0);
domain.push(["stage", "=", "Construction"]);
this.openAction("Project Construction", "tk.construction.project", domain);
}
async viewProjectHandover() {
const domain = await this.getProjectDomain(0);
domain.push(["stage", "=", "Handover"]);
this.openAction("Project Handover", "tk.construction.project", domain);
}
async getProjectDomain(index = 0) {
// const res = await this.rpc({
// model: "tk.construction.dashboard",
// method: "get_construction_project_domain",
// args: [this.state.selectedSite, this.state.selectedProject],
// });
const res = await this.orm.call("tk.construction.dashboard", "get_construction_project_domain", [this.state.selectedSite, this.state.selectedProject]);
return res && res.length ? res[index] : [];
}
renderCharts(data) {
if (!window.ApexCharts) return;
// Site State
this.renderGraph(this.refs.siteStateChart.el, {
chart: { type: "donut", height: 410 },
series: data.site_state?.[1] || [],
labels: data.site_state?.[0] || [],
legend: { position: "bottom" },
dataLabels: { enabled: false },
});
// Material Requisition
this.renderGraph(this.refs.mrqStateChart.el, {
chart: { type: "pie", height: 410 },
series: data.mrq_state?.[1] || [],
labels: data.mrq_state?.[0] || [],
legend: { position: "bottom" },
dataLabels: { enabled: false },
});
// Internal Transfer
this.renderGraph(this.refs.internalTransferChart.el, {
chart: { type: "donut", height: 410 },
series: data.internal_state?.[1] || [],
labels: data.internal_state?.[0] || [],
legend: { position: "bottom" },
dataLabels: { enabled: false },
});
// Project Timeline
this.renderTimeline(this.refs.projectTimeline.el, data.project_timeline || [], "Project");
// Site Timeline
this.renderTimeline(this.refs.constructionTimeline.el, data.site_timeline || [], "Timeline");
// Project Status
this.renderGraph(this.refs.projectStatusChart.el, {
chart: { type: "donut", height: 410 },
series: data.project_status?.[1] || [],
labels: data.project_status?.[0] || [],
legend: { position: "bottom" },
dataLabels: { enabled: false },
});
// Job Order PO
this.renderGraph(this.refs.jobOrderPoChart.el, {
chart: { type: "line", height: 350, zoom: { enabled: true } },
series: [
{ name: "Material PO", data: data.job_order_po?.[1] || [] },
{ name: "Equipment PO", data: data.job_order_po?.[2] || [] },
{ name: "Labour PO", data: data.job_order_po?.[3] || [] },
{ name: "Overhead PO", data: data.job_order_po?.[4] || [] },
],
xaxis: { categories: data.job_order_po?.[0] || [] },
dataLabels: { enabled: false },
legend: {
tooltipHoverFormatter: function (val, opts) {
return `${val} - ${opts.w.globals.series[opts.seriesIndex][opts.dataPointIndex]}`;
},
},
});
}
renderGraph(el, options) {
if (!el) return;
el.innerHTML = "";
const chart = new ApexCharts(el, options);
chart.render();
}
renderTimeline(el, timelineData, label = "Timeline") {
const series = (timelineData || []).map((entry) => ({
name: entry.name,
data: [
{
x: label,
y: [new Date(entry.start_date).getTime(), new Date(entry.end_date).getTime()],
},
],
}));
this.renderGraph(el, {
chart: { height: 350, type: "rangeBar" },
plotOptions: { bar: { horizontal: true } },
series,
xaxis: { type: "datetime" },
dataLabels: {
enabled: true,
formatter: function (val) {
const diff = (val[1] - val[0]) / (1000 * 60 * 60 * 24);
return `${diff} ${diff > 1 ? "days" : "day"}`;
},
},
});
}
}
ConstructionDashboard.template = "tk_construction_management.ConstructionDashboard";
registry.category("actions").add("construction_dashboard", ConstructionDashboard);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="tk_construction_management.ConstructionDashboard" owl="1">
<section>
<div class="container construction_dashboard">
<div class="row">
<div class="col-md-12 mb-4 mt32 mb16">
<div class="row">
<div class="col-sm-12 col-md-5">
<h2 class="section-header">Construction</h2>
</div>
<div class="col-sm-12 col-md-7">
<form class="form-group d-flex just-end">
<select class="form-control tk-form-control col-md-4 mr8"
t-on-change="onSiteChange">
<option value="all_site">All Projects</option>
<t t-foreach="state.sites" t-as="site" t-key="site.id">
<option t-att-value="site.id"
t-att-selected="site.id === state.selectedSite">
<t t-esc="site.name"/>
</option>
</t>
</select>
<select class="form-control tk-form-control col-md-4 mr8"
t-on-change="onProjectChange">
<option value="all_project">All Sub Projects</option>
<t t-foreach="state.projects" t-as="project" t-key="project.id">
<option t-att-value="project.id"
t-att-selected="project.id === state.selectedProject">
<t t-esc="project.name"/>
</option>
</t>
</select>
</form>
</div>
</div>
<hr/>
</div>
</div>
<!-- STATS CARDS -->
<div class="row">
<t t-set="cards" t-value="[
{ id: 'total_site', class: 'total-site', label: 'Project', stat: state.stats.total_site, onClick: viewTotalSite },
{ id: 'total_project', class: 'total-project', label: 'Sub Project', stat: state.stats.total_project, onClick: viewTotalProject },
{ id: 'total_mrq', class: 'total-mrq', label: 'Material Requisition', stat: state.stats.total_mrq, onClick: viewTotalMrq },
{ id: 'job_sheet_count', class: 'job-sheet', label: 'Project Phase(WBS)', stat: state.stats.job_sheet_count, onClick: viewTotalJobSheet },
{ id: 'job_order_count', class: 'job-order', label: 'Work Order', stat: state.stats.job_order_count, onClick: viewTotalJobOrder }
]"/>
<t t-foreach="cards" t-as="card" t-key="card.id">
<div class="col-md-4 col-lg-4 col-sm-6" t-attf-class="#{card.class}" t-on-click="card.onClick">
<div class="card card-xl-stretch mb-xl-8">
<div class="card-body p-0">
<div class="d-flex flex-stack card-p flex-grow-1">
<span class="symbol symbol-50px me-2">
<span class="symbol-label bg-light-info">
<span class="svg-icon svg-icon-info svg-icon-2x">
<!-- SVG Icon can be generalized or dynamic if needed -->
<i class="fa fa-cube"/>
</span>
</span>
</span>
<div class="d-flex flex-column text-end">
<span class="text-dark fw-bolder fs-2" t-att-id="card.id">
<t t-esc="card.stat || 0"/>
</span>
<span class="text-muted fw-bold mt-1">
<t t-esc="card.label"/>
</span>
</div>
</div>
</div>
</div>
</div>
</t>
</div>
<!-- PROJECT STAGES -->
<div class="row mt32">
<t t-set="stages" t-value="[
{ id: 'project_planning', label: 'Planning', class: 'project-planning', onClick: viewProjectPlanning },
{ id: 'project_procurement', label: 'Procurement', class: 'project-procurement', onClick: viewProjectProcurement },
{ id: 'project_construction', label: 'Construction', class: 'project-construction', onClick: viewProjectConstruction },
{ id: 'project_handover', label: 'Handover', class: 'project-handover', onClick: viewProjectHandover }
]"/>
<t t-foreach="stages" t-as="stage" t-key="stage.id">
<div class="col-md-4 col-lg-4 col-sm-6" t-attf-class="#{stage.class}"
t-on-click="stage.onClick">
<div class="card card-xl-stretch mb-xl-8">
<div class="card-body p-0">
<div class="d-flex flex-stack card-p flex-grow-1">
<span class="symbol symbol-50px me-2">
<span class="symbol-label bg-light-warning">
<i class="fa fa-cogs"/>
</span>
</span>
<div class="d-flex flex-column text-end">
<span class="text-dark fw-bolder fs-2" t-att-id="stage.id">
<t t-esc="state.stats.project_status?.[1]?.[stage.id] === 1 ? 1 : 0"/>
</span>
<span class="text-muted fw-bold mt-1">
<t t-esc="stage.label"/>
</span>
</div>
</div>
</div>
</div>
</div>
</t>
</div>
<!-- CHARTS -->
<div class="mt32 mb16">
<h2>Construction Statistics</h2>
</div>
<div class="row">
<div class="col-md-8">
<div class="box mt16 mb16">
<h5 class="mb16">Project Timeline</h5>
<div t-ref="constructionTimeline" style="min-height: 350px"/>
</div>
</div>
<div class="col-md-4">
<div class="box mt16 mb16">
<h5 class="mb16">Site State</h5>
<div t-ref="siteStateChart" style="min-height: 410px"/>
</div>
</div>
<div class="col-md-4">
<div class="box mt16 mb16">
<h5 class="mb16">Sub Project Status</h5>
<div t-ref="projectStatusChart" style="min-height: 410px"/>
</div>
</div>
<div class="col-md-8">
<div class="box mt16 mb16">
<h5 class="mb16">Sub Project Timeline</h5>
<div t-ref="projectTimeline" style="min-height: 350px"/>
</div>
</div>
<div class="col-md-6">
<div class="box mt16 mb16">
<h5 class="mb16">Material Requisition</h5>
<div t-ref="mrqStateChart" style="min-height: 410px"/>
</div>
</div>
<div class="col-md-6">
<div class="box mt16 mb16">
<h5 class="mb16">Internal Transfer</h5>
<div t-ref="internalTransferChart" style="min-height: 410px"/>
</div>
</div>
<div class="col-md-12">
<div class="box mt16 mb16">
<h5 class="mb16">Job Order Purchase Order</h5>
<div t-ref="jobOrderPoChart" style="min-height: 350px"/>
</div>
</div>
</div>
</div>
</section>
</t>
</templates>